diff --git a/.env b/.env new file mode 100644 index 0000000..b9e2bf0 --- /dev/null +++ b/.env @@ -0,0 +1,3 @@ +DB_NAME=norahuniversity +DB_USER=norahuniversity +DB_PASSWORD=norahuniversity \ No newline at end of file diff --git a/NorahUniversity/__pycache__/settings.cpython-313.pyc b/NorahUniversity/__pycache__/settings.cpython-313.pyc deleted file mode 100644 index 19cff8c..0000000 Binary files a/NorahUniversity/__pycache__/settings.cpython-313.pyc and /dev/null differ diff --git a/NorahUniversity/__pycache__/urls.cpython-313.pyc b/NorahUniversity/__pycache__/urls.cpython-313.pyc index 2aebbd2..0a83457 100644 Binary files a/NorahUniversity/__pycache__/urls.cpython-313.pyc and b/NorahUniversity/__pycache__/urls.cpython-313.pyc differ diff --git a/NorahUniversity/settings.py b/NorahUniversity/settings.py index a402e87..58cb627 100644 --- a/NorahUniversity/settings.py +++ b/NorahUniversity/settings.py @@ -9,10 +9,13 @@ 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 +from dotenv import load_dotenv +load_dotenv() # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -20,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 @@ -30,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 @@ -135,9 +136,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application' DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', - 'NAME': 'haikal_db', - 'USER': 'faheed', - 'PASSWORD': 'Faheed@215', + 'NAME': os.getenv("DB_NAME"), + 'USER': os.getenv("DB_USER"), + 'PASSWORD': os.getenv("DB_PASSWORD"), 'HOST': '127.0.0.1', 'PORT': '5432', } @@ -155,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', @@ -171,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" @@ -193,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 @@ -224,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) @@ -262,146 +278,200 @@ FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB CORS_ALLOW_CREDENTIALS = True -CELERY_BROKER_URL = 'redis://localhost:6379/0' # Or your message broker URL -CELERY_RESULT_BACKEND = 'django-db' # If using django-celery-results -CELERY_ACCEPT_CONTENT = ['application/json'] -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' -CELERY_TIMEZONE = 'UTC' +CELERY_BROKER_URL = "redis://localhost:6379/0" # Or your message broker URL +CELERY_RESULT_BACKEND = "django-db" # If using django-celery-results +CELERY_ACCEPT_CONTENT = ["application/json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" +CELERY_TIMEZONE = "UTC" -LINKEDIN_CLIENT_ID = '867jwsiyem1504' -LINKEDIN_CLIENT_SECRET = 'WPL_AP1.QNH5lYnfRSQpp0Qp.GO8Srw==' -LINKEDIN_REDIRECT_URI = 'http://127.0.0.1:8000/jobs/linkedin/callback/' +LINKEDIN_CLIENT_ID = "867jwsiyem1504" +LINKEDIN_CLIENT_SECRET = "WPL_AP1.QNH5lYnfRSQpp0Qp.GO8Srw==" +LINKEDIN_REDIRECT_URI = "http://127.0.0.1:8000/jobs/linkedin/callback/" Q_CLUSTER = { - 'name': 'KAAUH_CLUSTER', - 'workers': 8, - 'recycle': 500, - 'timeout': 60, - 'max_attempts': 1, - 'compress': True, - 'save_limit': 250, - 'queue_limit': 500, - 'cpu_affinity': 1, - 'label': 'Django Q2', - 'redis': { - 'host': '127.0.0.1', - 'port': 6379, - 'db': 3, }, - 'ALT_CLUSTERS': { - 'long': { - 'timeout': 3000, - 'retry': 3600, - 'max_attempts': 2, + "name": "KAAUH_CLUSTER", + "workers": 8, + "recycle": 500, + "timeout": 60, + "max_attempts": 1, + "compress": True, + "save_limit": 250, + "queue_limit": 500, + "cpu_affinity": 1, + "label": "Django Q2", + "redis": { + "host": "127.0.0.1", + "port": 6379, + "db": 3, + }, + "ALT_CLUSTERS": { + "long": { + "timeout": 3000, + "retry": 3600, + "max_attempts": 2, }, - 'short': { - 'timeout': 10, - 'max_attempts': 1, + "short": { + "timeout": 10, + "max_attempts": 1, }, - } + }, } customColorPalette = [ - { - 'color': 'hsl(4, 90%, 58%)', - 'label': 'Red' - }, - { - 'color': 'hsl(340, 82%, 52%)', - 'label': 'Pink' - }, - { - 'color': 'hsl(291, 64%, 42%)', - 'label': 'Purple' - }, - { - 'color': 'hsl(262, 52%, 47%)', - 'label': 'Deep Purple' - }, - { - 'color': 'hsl(231, 48%, 48%)', - 'label': 'Indigo' - }, - { - 'color': 'hsl(207, 90%, 54%)', - 'label': 'Blue' - }, - ] + {"color": "hsl(4, 90%, 58%)", "label": "Red"}, + {"color": "hsl(340, 82%, 52%)", "label": "Pink"}, + {"color": "hsl(291, 64%, 42%)", "label": "Purple"}, + {"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"}, + {"color": "hsl(231, 48%, 48%)", "label": "Indigo"}, + {"color": "hsl(207, 90%, 54%)", "label": "Blue"}, +] # CKEDITOR_5_CUSTOM_CSS = 'path_to.css' # optional # CKEDITOR_5_FILE_STORAGE = "path_to_storage.CustomStorage" # optional CKEDITOR_5_CONFIGS = { - 'default': { - 'toolbar': { - 'items': ['heading', '|', 'bold', 'italic', 'link', - 'bulletedList', 'numberedList', 'blockQuote', 'imageUpload', ], - } - + "default": { + "toolbar": { + "items": [ + "heading", + "|", + "bold", + "italic", + "link", + "bulletedList", + "numberedList", + "blockQuote", + "imageUpload", + ], + } }, - 'extends': { - 'blockToolbar': [ - 'paragraph', 'heading1', 'heading2', 'heading3', - '|', - 'bulletedList', 'numberedList', - '|', - 'blockQuote', + "extends": { + "blockToolbar": [ + "paragraph", + "heading1", + "heading2", + "heading3", + "|", + "bulletedList", + "numberedList", + "|", + "blockQuote", ], - 'toolbar': { - 'items': ['heading', '|', 'outdent', 'indent', '|', 'bold', 'italic', 'link', 'underline', 'strikethrough', - 'code','subscript', 'superscript', 'highlight', '|', 'codeBlock', 'sourceEditing', 'insertImage', - 'bulletedList', 'numberedList', 'todoList', '|', 'blockQuote', 'imageUpload', '|', - 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'mediaEmbed', 'removeFormat', - 'insertTable', - ], - 'shouldNotGroupWhenFull': 'true' + "toolbar": { + "items": [ + "heading", + "|", + "outdent", + "indent", + "|", + "bold", + "italic", + "link", + "underline", + "strikethrough", + "code", + "subscript", + "superscript", + "highlight", + "|", + "codeBlock", + "sourceEditing", + "insertImage", + "bulletedList", + "numberedList", + "todoList", + "|", + "blockQuote", + "imageUpload", + "|", + "fontSize", + "fontFamily", + "fontColor", + "fontBackgroundColor", + "mediaEmbed", + "removeFormat", + "insertTable", + ], + "shouldNotGroupWhenFull": "true", }, - 'image': { - 'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft', - 'imageStyle:alignRight', 'imageStyle:alignCenter', 'imageStyle:side', '|'], - 'styles': [ - 'full', - 'side', - 'alignLeft', - 'alignRight', - 'alignCenter', - ] - + "image": { + "toolbar": [ + "imageTextAlternative", + "|", + "imageStyle:alignLeft", + "imageStyle:alignRight", + "imageStyle:alignCenter", + "imageStyle:side", + "|", + ], + "styles": [ + "full", + "side", + "alignLeft", + "alignRight", + "alignCenter", + ], }, - 'table': { - 'contentToolbar': [ 'tableColumn', 'tableRow', 'mergeTableCells', - 'tableProperties', 'tableCellProperties' ], - 'tableProperties': { - 'borderColors': customColorPalette, - 'backgroundColors': customColorPalette + "table": { + "contentToolbar": [ + "tableColumn", + "tableRow", + "mergeTableCells", + "tableProperties", + "tableCellProperties", + ], + "tableProperties": { + "borderColors": customColorPalette, + "backgroundColors": customColorPalette, + }, + "tableCellProperties": { + "borderColors": customColorPalette, + "backgroundColors": customColorPalette, }, - 'tableCellProperties': { - 'borderColors': customColorPalette, - 'backgroundColors': customColorPalette - } }, - 'heading' : { - 'options': [ - { 'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph' }, - { 'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1' }, - { 'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2' }, - { 'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3' } + "heading": { + "options": [ + { + "model": "paragraph", + "title": "Paragraph", + "class": "ck-heading_paragraph", + }, + { + "model": "heading1", + "view": "h1", + "title": "Heading 1", + "class": "ck-heading_heading1", + }, + { + "model": "heading2", + "view": "h2", + "title": "Heading 2", + "class": "ck-heading_heading2", + }, + { + "model": "heading3", + "view": "h3", + "title": "Heading 3", + "class": "ck-heading_heading3", + }, ] + }, + }, + "list": { + "properties": { + "styles": "true", + "startIndex": "true", + "reversed": "true", } }, - 'list': { - 'properties': { - 'styles': 'true', - 'startIndex': 'true', - 'reversed': 'true', - } - } } # Define a constant in settings.py to specify file upload permissions -CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any" - +CKEDITOR_5_FILE_UPLOAD_PERMISSION = ( + "staff" # Possible values: "staff", "authenticated", "any" +) @@ -409,3 +479,7 @@ from django.contrib.messages import constants as messages MESSAGE_TAGS = { messages.ERROR: 'danger', } + + +# Custom User Model +AUTH_USER_MODEL = "recruitment.CustomUser" diff --git a/NorahUniversity/urls.py b/NorahUniversity/urls.py index 432af79..555b9ce 100644 --- a/NorahUniversity/urls.py +++ b/NorahUniversity/urls.py @@ -26,6 +26,7 @@ urlpatterns = [ path('application//', views.application_submit_form, name='application_submit_form'), path('application//submit/', views.application_submit, name='application_submit'), path('application//apply/', views.application_detail, name='application_detail'), + path('application//signup/', views.candidate_signup, name='candidate_signup'), path('application//success/', views.application_success, name='application_success'), path('application/applicant/profile', views.applicant_profile, name='applicant_profile'), diff --git a/comprehensive_translation_merger.py b/comprehensive_translation_merger.py new file mode 100644 index 0000000..c131ab6 --- /dev/null +++ b/comprehensive_translation_merger.py @@ -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() diff --git a/empty_translations_summary.txt b/empty_translations_summary.txt new file mode 100644 index 0000000..44714a5 --- /dev/null +++ b/empty_translations_summary.txt @@ -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" diff --git a/locale/ar/LC_MESSAGES/django.po b/locale/ar/LC_MESSAGES/django.po index a97763b..9f5332b 100644 --- a/locale/ar/LC_MESSAGES/django.po +++ b/locale/ar/LC_MESSAGES/django.po @@ -8,11 +8,15 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" +<<<<<<< HEAD "POT-Creation-Date: 2025-11-03 13:54+0300\n" +======= +"POT-Creation-Date: 2025-11-03 12:14+0300\n" +>>>>>>> update1 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: \n" +"Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -42,10 +46,17 @@ msgstr "اسم العائلة" #: templates/recruitment/agency_list.html:187 #: templates/recruitment/agency_portal_assignment_detail.html:560 msgid "Phone" +<<<<<<< HEAD msgstr "الهاتف" #: recruitment/forms.py:243 recruitment/models.py:426 #: recruitment/models.py:1683 recruitment/models.py:1757 +======= +msgstr "رقم الهاتف" + +#: recruitment/forms.py:243 recruitment/models.py:426 +#: recruitment/models.py:1664 recruitment/models.py:1738 +>>>>>>> update1 #: templates/jobs/job_candidates_list.html:227 #: templates/meetings/meeting_details.html:305 #: templates/participants/participants_list.html:213 @@ -57,16 +68,21 @@ msgstr "الهاتف" #: templates/recruitment/candidate_detail.html:337 #: templates/recruitment/candidate_list.html:270 #: templates/recruitment/notification_list.html:50 +<<<<<<< HEAD #: templates/user/admin_settings.html:176 +======= +#: templates/user/admin_settings.html:175 +>>>>>>> update1 msgid "Email" msgstr "البريد الإلكتروني" #: recruitment/forms.py:244 recruitment/forms.py:1067 recruitment/models.py:429 msgid "Resume" -msgstr "" +msgstr "السيرة الذاتية" #: recruitment/forms.py:245 msgid "Hiring Type" +<<<<<<< HEAD msgstr "" #: recruitment/forms.py:246 recruitment/models.py:180 recruitment/models.py:501 @@ -102,26 +118,69 @@ msgid "Enter email" msgstr "أدخل الملاحظات" #: recruitment/forms.py:281 +======= +msgstr "نوع التوظيف" + +#: recruitment/forms.py:246 recruitment/models.py:180 recruitment/models.py:505 +#: recruitment/models.py:1291 +msgid "Hiring Agency" +msgstr "وكالة التوظيف" + +#: recruitment/forms.py:249 +#: templates/recruitment/agency_portal_submit_candidate.html:194 +msgid "Enter first name" +msgstr "أدخل الاسم الأول" + +#: recruitment/forms.py:250 +#: templates/recruitment/agency_portal_submit_candidate.html:205 +msgid "Enter last name" +msgstr "أدخل اسم العائلة" + +#: recruitment/forms.py:251 +#: templates/recruitment/agency_portal_submit_candidate.html:237 +msgid "Enter phone number" +msgstr "أدخل رقم الهاتف" + +#: recruitment/forms.py:252 +msgid "Enter email" +msgstr "أدخل البريد الإلكتروني" + +#: recruitment/forms.py:281 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:56 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_form.html:21 +>>>>>>> update1 msgid "Submit" msgstr "إرسال" #: recruitment/forms.py:292 +<<<<<<< HEAD #, fuzzy #| msgid "Next Action Date" msgid "New Application Stage" msgstr "تاريخ الإجراء التالي" #: recruitment/forms.py:303 recruitment/models.py:736 +======= +msgid "New Application Stage" +msgstr "مرحلة تقديم جديدة" + +#: recruitment/forms.py:303 recruitment/models.py:717 +>>>>>>> update1 #: templates/includes/meeting_form.html:10 #: templates/meetings/create_meeting.html:162 #: templates/meetings/list_meetings.html:306 #: templates/meetings/update_meeting.html:215 #: templates/recruitment/candidate_interview_view.html:263 msgid "Topic" -msgstr "" +msgstr "الموضوع" +<<<<<<< HEAD #: recruitment/forms.py:304 recruitment/models.py:740 #: recruitment/models.py:1587 recruitment/models.py:1606 +======= +#: recruitment/forms.py:304 recruitment/models.py:721 +#: recruitment/models.py:1568 recruitment/models.py:1587 +>>>>>>> update1 #: templates/interviews/schedule_interviews.html:169 #: templates/interviews/schedule_interviews.html:201 #: templates/meetings/create_meeting.html:166 @@ -133,50 +192,81 @@ msgstr "" msgid "Start Time" msgstr "وقت البدء" +<<<<<<< HEAD #: recruitment/forms.py:305 recruitment/models.py:742 +======= +#: recruitment/forms.py:305 recruitment/models.py:723 +>>>>>>> update1 #: templates/meetings/list_meetings.html:266 #: templates/meetings/list_meetings.html:311 #: templates/meetings/meeting_details.html:265 #: templates/recruitment/candidate_interview_view.html:264 +<<<<<<< HEAD +======= +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1868 +>>>>>>> update1 msgid "Duration" msgstr "المدة" #: recruitment/forms.py:308 msgid "Enter meeting topic" -msgstr "" +msgstr "أدخل موضوع الاجتماع" #: recruitment/forms.py:310 msgid "60" -msgstr "" +msgstr "60" #: recruitment/forms.py:324 templates/meetings/create_meeting.html:180 #: templates/meetings/list_meetings.html:179 +<<<<<<< HEAD #, fuzzy #| msgid "Sales Meeting" msgid "Create Meeting" msgstr "اجتماع مبيعات" #: recruitment/forms.py:332 recruitment/models.py:711 +======= +msgid "Create Meeting" +msgstr "إنشاء اجتماع" + +#: recruitment/forms.py:332 recruitment/models.py:692 +>>>>>>> update1 #: templates/recruitment/training_list.html:204 #: templates/recruitment/training_update.html:144 msgid "Title" msgstr "العنوان" +<<<<<<< HEAD #: recruitment/forms.py:333 recruitment/models.py:712 recruitment/models.py:806 +======= +#: recruitment/forms.py:333 recruitment/models.py:693 recruitment/models.py:787 +>>>>>>> update1 #: templates/recruitment/training_update.html:158 #, fuzzy #| msgid "Contact" msgid "Content" +<<<<<<< HEAD msgstr "جهة الاتصال" #: recruitment/forms.py:334 recruitment/models.py:713 +======= +msgstr "المحتوى" + +#: recruitment/forms.py:334 recruitment/models.py:694 +>>>>>>> update1 #: templates/recruitment/training_update.html:150 msgid "Video Link" -msgstr "" +msgstr "رابط الفيديو" +<<<<<<< HEAD #: recruitment/forms.py:335 recruitment/models.py:715 +======= +#: recruitment/forms.py:335 recruitment/models.py:696 +>>>>>>> update1 #: templates/recruitment/training_update.html:166 +#: venv/lib/python3.13/site-packages/django/db/models/fields/files.py:244 msgid "File" +<<<<<<< HEAD msgstr "الملف" #: recruitment/forms.py:338 @@ -205,6 +295,30 @@ msgstr "تاريخ الإنشاء" #: recruitment/forms.py:516 recruitment/models.py:422 #: recruitment/models.py:1334 templates/forms/form_templates_list.html:270 +======= +msgstr "ملف" + +#: recruitment/forms.py:338 +#, fuzzy +#| msgid "Enter material content" +msgid "Enter material title" +msgstr "أدخل عنوان المادة" + +#: recruitment/forms.py:339 +msgid "Enter material content" +msgstr "أدخل محتوى المادة" + +#: recruitment/forms.py:340 +msgid "https://www.youtube.com/watch?v=..." +msgstr "https://www.youtube.com/watch?v=..." + +#: recruitment/forms.py:359 +msgid "Create Material" +msgstr "إنشاء مادة" + +#: recruitment/forms.py:516 recruitment/models.py:422 +#: recruitment/models.py:1315 templates/forms/form_templates_list.html:270 +>>>>>>> update1 #: templates/meetings/list_meetings.html:256 #: templates/meetings/list_meetings.html:308 #: templates/meetings/reschedule_meeting.html:11 @@ -214,6 +328,7 @@ msgstr "تاريخ الإنشاء" #: templates/recruitment/candidate_list.html:273 #: templates/recruitment/schedule_meeting_form.html:18 msgid "Job" +<<<<<<< HEAD msgstr "" #: recruitment/forms.py:517 templates/forms/form_templates_list.html:269 @@ -11035,10 +11150,8904 @@ msgstr "تاريخ الإبطال" #~ msgid "Car Transfer Approve" #~ msgstr "الموافقة على نقل السيارة" +======= +msgstr "الوظيفة" + +#: recruitment/forms.py:517 templates/forms/form_templates_list.html:269 +msgid "Template Name" +msgstr "اسم القالب" + +#: recruitment/forms.py:518 recruitment/models.py:1101 +#: templates/recruitment/agency_detail.html:377 +msgid "Description" +msgstr "الوصف" + +#: recruitment/forms.py:519 recruitment/models.py:1135 +#: recruitment/models.py:1300 templates/jobs/job_list.html:230 +#: templates/recruitment/agency_access_link_detail.html:31 +#: templates/recruitment/agency_access_link_form.html:89 +#: templates/recruitment/agency_assignment_list.html:87 +#: templates/recruitment/agency_detail.html:456 +#: templates/recruitment/agency_portal_dashboard.html:148 +#: templates/user/admin_settings.html:192 +msgid "Active" +msgstr "نشط" + +#: recruitment/forms.py:524 +msgid "Enter template name" +msgstr "أدخل اسم القالب" + +#: recruitment/forms.py:530 +msgid "Enter template description (optional)" +msgstr "أدخل وصف القالب (اختياري)" + +#: recruitment/forms.py:549 templates/forms/form_templates_list.html:384 +msgid "Create Template" +msgstr "إنشاء قالب" + +#: recruitment/forms.py:620 +msgid "Enter your comment or note" +msgstr "أدخل تعليقك أو ملاحظتك" + +#: recruitment/forms.py:625 templates/meetings/meeting_details.html:422 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:23 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:59 +msgid "Comment" +msgstr "تعليق" + +#: recruitment/forms.py:637 +msgid "Add Comment" +msgstr "إضافة تعليق" + +#: recruitment/forms.py:768 recruitment/models.py:1276 +#: templates/recruitment/agency_confirm_delete.html:218 +#: templates/recruitment/agency_list.html:184 +msgid "Agency Name" +msgstr "اسم الوكالة" + +#: recruitment/forms.py:769 recruitment/models.py:1278 +#: templates/recruitment/agency_confirm_delete.html:229 +#: templates/recruitment/agency_list.html:185 +msgid "Contact Person" +msgstr "شخص الاتصال" + +#: recruitment/forms.py:770 recruitment/forms.py:1065 +#: templates/participants/participants_detail.html:172 +#: templates/recruitment/agency_portal_submit_candidate.html:219 +#: templates/user/profile.html:139 +#, fuzzy +#| msgid "IP Address" +msgid "Email Address" +msgstr "عنوان البريد الإلكتروني" + +#: recruitment/forms.py:771 recruitment/forms.py:1066 recruitment/models.py:32 +#: recruitment/models.py:1739 templates/meetings/meeting_details.html:306 +#: templates/participants/participants_detail.html:178 +#: templates/recruitment/agency_portal_submit_candidate.html:230 +msgid "Phone Number" +msgstr "رقم الهاتف" + +#: recruitment/forms.py:772 templates/recruitment/agency_detail.html:261 +#: templates/recruitment/agency_detail.html:313 +#: templates/recruitment/agency_list.html:189 +msgid "Website" +msgstr "الموقع الإلكتروني" + +#: recruitment/forms.py:773 templates/jobs/create_job.html:302 +#: templates/jobs/edit_job.html:302 +#: templates/recruitment/agency_detail.html:363 +#: templates/recruitment/agency_list.html:188 +msgid "Country" +msgstr "الدولة" + +#: recruitment/forms.py:774 recruitment/models.py:428 +#: templates/recruitment/agency_detail.html:339 +#: templates/recruitment/agency_portal_assignment_detail.html:568 +#, fuzzy +#| msgid "IP Address" +msgid "Address" +msgstr "العنوان" + +#: recruitment/forms.py:775 +#, fuzzy +#| msgid "Internal Job ID:" +msgid "Internal Notes" +msgstr "ملاحظات داخلية" + +#: recruitment/forms.py:799 +#, fuzzy +#| msgid "Submitted by Agency" +msgid "Save Agency" +msgstr "حفظ الوكالة" + +#: recruitment/forms.py:888 recruitment/models.py:495 +#: recruitment/models.py:1309 +#: templates/recruitment/agency_access_link_detail.html:46 +#: templates/recruitment/agency_assignment_detail.html:124 +#: templates/recruitment/agency_assignment_list.html:112 +msgid "Agency" +msgstr "وكالة" + +#: recruitment/forms.py:889 +msgid "Job Posting" +msgstr "إعلان الوظيفة" + +#: recruitment/forms.py:890 recruitment/models.py:1320 +#: templates/recruitment/agency_portal_assignment_detail.html:174 +msgid "Maximum Candidates" +msgstr "الحد الأقصى للمرشحين" + +#: recruitment/forms.py:891 recruitment/models.py:1332 +msgid "Deadline Date" +msgstr "الموعد النهائي" + +#: recruitment/forms.py:892 recruitment/forms.py:987 recruitment/models.py:1337 +#: recruitment/models.py:1505 +#, fuzzy +#| msgid "Active" +msgid "Is Active" +msgstr "هل هو نشط" + +#: recruitment/forms.py:893 recruitment/models.py:750 +#: recruitment/models.py:1342 recruitment/models.py:1691 +#: templates/includes/candidate_modal_body.html:70 +#: templates/includes/easy_logs.html:209 +#: templates/meetings/list_meetings.html:312 +#: templates/recruitment/agency_access_link_form.html:84 +#: templates/recruitment/agency_assignment_detail.html:136 +#: templates/recruitment/agency_assignment_list.html:84 +#: templates/recruitment/agency_assignment_list.html:116 +#: templates/recruitment/agency_portal_assignment_detail.html:148 +#: templates/recruitment/candidate_detail.html:557 +#: templates/recruitment/candidate_hired_view.html:236 +#: templates/recruitment/notification_detail.html:155 +#: templates/recruitment/notification_list.html:37 +#: templates/recruitment/partials/_candidate_table.html:12 +#: templates/user/admin_settings.html:176 +msgid "Status" +msgstr "الحالة" + +#: recruitment/forms.py:894 recruitment/models.py:1360 +#: templates/recruitment/agency_assignment_detail.html:160 +msgid "Admin Notes" +msgstr "ملاحظات المسؤول" + +#: recruitment/forms.py:928 +msgid "Save Assignment" +msgstr "حفظ التكليف" + +#: recruitment/forms.py:985 recruitment/models.py:1470 +#: templates/recruitment/agency_access_link_detail.html:37 +#: templates/recruitment/agency_access_link_form.html:37 +msgid "Assignment" +msgstr "التكليف" + +#: recruitment/forms.py:986 recruitment/models.py:1489 +#: templates/recruitment/agency_access_link_detail.html:56 +#: templates/recruitment/agency_access_link_form.html:52 +msgid "Expires At" +msgstr "ينتهي في" + +#: recruitment/forms.py:1011 +#: templates/recruitment/agency_access_link_form.html:4 +#: templates/recruitment/agency_access_link_form.html:12 +#: templates/recruitment/agency_access_link_form.html:130 +#, fuzzy +#| msgid "Create Meeting" +msgid "Create Access Link" +msgstr "إنشاء رابط وصول" + +#: recruitment/forms.py:1091 +#: templates/recruitment/agency_portal_dashboard.html:198 +#: templates/recruitment/agency_portal_submit_candidate.html:4 +#: templates/recruitment/agency_portal_submit_candidate.html:104 +#: templates/recruitment/agency_portal_submit_candidate.html:337 +#: templates/recruitment/agency_portal_submit_candidate.html:561 +#, fuzzy +#| msgid "View Candidate" +msgid "Submit Candidate" +msgstr "تقديم المرشح" + +#: recruitment/forms.py:1155 +#: templates/recruitment/agency_access_link_detail.html:98 +#: templates/recruitment/agency_assignment_detail.html:187 +#: templates/recruitment/agency_portal_login.html:145 +msgid "Access Token" +msgstr "رمز الوصول" + +#: recruitment/forms.py:1164 recruitment/models.py:733 +#: templates/account/login.html:164 templates/meetings/list_meetings.html:266 +#: templates/recruitment/agency_access_link_detail.html:109 +#: templates/recruitment/agency_assignment_detail.html:198 +#: templates/recruitment/agency_portal_login.html:167 +msgid "Password" +msgstr "كلمة المرور" + +#: recruitment/forms.py:1253 +#, fuzzy +#| msgid "Participant Video" +msgid "Select Participants" +msgstr "اختر المشاركين" + +#: recruitment/forms.py:1259 +#, fuzzy +#| msgid "Select country" +msgid "Select Users" +msgstr "اختر المستخدمين" + +#: recruitment/forms.py:1276 templates/includes/email_compose_form.html:20 +#: templates/recruitment/agency_portal_assignment_detail.html:488 +msgid "Subject" +msgstr "الموضوع" + +#: recruitment/forms.py:1287 templates/includes/email_compose_form.html:56 +#: templates/recruitment/agency_portal_assignment_detail.html:503 +#, fuzzy +#| msgid "Error Message" +msgid "Message" +msgstr "الرسالة" + +#: recruitment/forms.py:1295 templates/includes/email_compose_form.html:35 +msgid "Recipients" +msgstr "المستلمون" + +#: recruitment/forms.py:1303 +#, fuzzy +#| msgid "Candidate Information" +msgid "Include candidate information" +msgstr "تضمين معلومات المرشح" + +#: recruitment/forms.py:1312 +#, fuzzy +#| msgid "Meeting Details" +msgid "Include meeting details" +msgstr "تضمين تفاصيل الاجتماع" + +#: recruitment/forms.py:1376 +#, fuzzy +#| msgid "Please select a stage." +msgid "Please select at least one recipient." +msgstr "الرجاء اختيار مستلم واحد على الأقل." + +#: recruitment/models.py:20 +msgid "Created at" +msgstr "تم الإنشاء في" + +#: recruitment/models.py:21 +msgid "Updated at" +msgstr "تم التحديث في" + +#: recruitment/models.py:23 +msgid "Slug" +msgstr "الرابط المختصر" + +#: recruitment/models.py:43 +msgid "Full-time" +msgstr "دوام كامل" + +#: recruitment/models.py:44 +msgid "Part-time" +msgstr "دوام جزئي" + +#: recruitment/models.py:45 +msgid "Contract" +msgstr "عقد عمل" + +#: recruitment/models.py:46 +msgid "Internship" +msgstr "داخلي" + +#: recruitment/models.py:47 +msgid "Faculty" +msgstr "كلية" + +#: recruitment/models.py:48 +msgid "Temporary" +msgstr "موقت" + +#: recruitment/models.py:52 +msgid "On-site" +msgstr "في الموقع" + +#: recruitment/models.py:53 +msgid "Remote" +msgstr "عن بعد" + +#: recruitment/models.py:54 +msgid "Hybrid" +msgstr "مزيج" + +#: recruitment/models.py:60 +#, fuzzy +#| msgid "Internal Information" +msgid "Internal Participant" +msgstr "مشارك داخلي" + +#: recruitment/models.py:61 +msgid "Internal staff involved in the recruitment process for this job" +msgstr "الموظفون الداخليون المشاركون في عملية التوظيف لهذه الوظيفة" + +#: recruitment/models.py:66 +msgid "External Participant" +msgstr "مشارك خارجي" + +#: recruitment/models.py:67 +msgid "External participants involved in the recruitment process for this job" +msgstr "المشاركون الخارجيون المشاركون في عملية التوظيف لهذه الوظيفة" + +#: recruitment/models.py:182 +msgid "External agency responsible for sourcing candidates for this role" +msgstr "وكالة خارجية مسؤولة عن توفير المرشحين لهذا المنصب" + +#: recruitment/models.py:187 +msgid "Reason for canceling the job posting" +msgstr "سبب إلغاء نشر الوظيفة" + +#: recruitment/models.py:188 +#, fuzzy +#| msgid "Cancelled" +msgid "Cancel Reason" +msgstr "سبب الإلغاء" + +#: recruitment/models.py:193 +msgid "Name of person who cancelled this job" +msgstr "اسم الشخص الذي ألغى هذه الوظيفة" + +#: recruitment/models.py:194 +#, fuzzy +#| msgid "Cancelled" +msgid "Cancelled By" +msgstr "تم الإلغاء بواسطة" + +#: recruitment/models.py:392 recruitment/models.py:437 +#: templates/jobs/job_candidates_list.html:211 +#: templates/recruitment/candidate_list.html:231 +msgid "Applied" +msgstr "تم التقديم" + +#: recruitment/models.py:393 templates/jobs/job_candidates_list.html:212 +#: templates/jobs/job_list.html:283 +#: templates/jobs/partials/applicant_tracking.html:128 +#: templates/recruitment/candidate_detail.html:410 +#: templates/recruitment/candidate_list.html:232 +msgid "Exam" +msgstr "الاختبار" + +#: recruitment/models.py:394 templates/jobs/job_candidates_list.html:213 +#: templates/jobs/job_list.html:284 +#: templates/jobs/partials/applicant_tracking.html:144 +#: templates/recruitment/candidate_detail.html:424 +#: templates/recruitment/candidate_list.html:233 +msgid "Interview" +msgstr "المقابلة" + +#: recruitment/models.py:395 templates/jobs/job_candidates_list.html:214 +#: templates/jobs/job_list.html:285 +#: templates/jobs/partials/applicant_tracking.html:160 +#: templates/recruitment/candidate_detail.html:439 +#: templates/recruitment/candidate_list.html:234 +#: templates/recruitment/candidate_offer_view.html:248 +msgid "Offer" +msgstr "العرض" + +#: recruitment/models.py:396 +#: templates/jobs/partials/applicant_tracking.html:176 +#: templates/recruitment/agency_detail.html:462 +#: templates/recruitment/candidate_hired_view.html:273 +msgid "Hired" +msgstr "تم التوظيف" + +#: recruitment/models.py:399 +#: templates/includes/candidate_update_exam_form.html:5 +#: templates/includes/candidate_update_interview_form.html:5 +msgid "Passed" +msgstr "نجح" + +#: recruitment/models.py:400 recruitment/models.py:1671 +#: templates/includes/candidate_update_exam_form.html:8 +#: templates/includes/candidate_update_interview_form.html:8 +#: templates/includes/easy_logs.html:267 +msgid "Failed" +msgstr "رسب" + +#: recruitment/models.py:403 +#: templates/includes/candidate_update_offer_form.html:5 +msgid "Accepted" +msgstr "قبل" + +#: recruitment/models.py:404 +#: templates/includes/candidate_update_offer_form.html:8 +#: templates/recruitment/agency_detail.html:468 +msgid "Rejected" +msgstr "رفض" + +#: recruitment/models.py:407 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant" +msgstr "مقدم الطلب" + +#: recruitment/models.py:408 recruitment/models.py:509 +#: templates/meetings/list_meetings.html:248 +#: templates/meetings/list_meetings.html:307 +msgid "Candidate" +msgstr "المرشح" + +#: recruitment/models.py:431 +#, fuzzy +#| msgid "Resume" +msgid "Resume Parsed" +msgstr "السيرة الذاتية المحللة" + +#: recruitment/models.py:434 +#, fuzzy +#| msgid "Create Candidate" +msgid "Potential Candidate" +msgstr "مرشح محتمل" + +#: recruitment/models.py:436 +msgid "Parsed Summary" +msgstr "ملخص محلل" + +#: recruitment/models.py:442 templates/jobs/job_candidates_list.html:229 +#: templates/recruitment/agency_assignment_detail.html:241 +#: templates/recruitment/agency_portal_assignment_detail.html:243 +#: templates/recruitment/candidate_list.html:275 +#: templates/recruitment/partials/_candidate_table.html:13 +msgid "Stage" +msgstr "المرحلة" + +#: recruitment/models.py:450 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Status" +msgstr "حالة مقدم الطلب" + +#: recruitment/models.py:452 templates/recruitment/candidate_exam_view.html:253 +msgid "Exam Date" +msgstr "تاريخ الاختبار" + +#: recruitment/models.py:458 +msgid "Exam Status" +msgstr "حالة الاختبار" + +#: recruitment/models.py:461 recruitment/models.py:1634 +msgid "Interview Date" +msgstr "تاريخ المقابلة" + +#: recruitment/models.py:468 +msgid "Interview Status" +msgstr "حالة المقابلة" + +#: recruitment/models.py:470 +msgid "Offer Date" +msgstr "تاريخ العرض" + +#: recruitment/models.py:476 +msgid "Offer Status" +msgstr "حالة العرض" + +#: recruitment/models.py:478 +#: templates/recruitment/candidate_hired_view.html:235 +#, fuzzy +#| msgid "Applied Date" +msgid "Hired Date" +msgstr "تاريخ التوظيف" + +#: recruitment/models.py:479 +msgid "Join Date" +msgstr "تاريخ الانضمام" + +#: recruitment/models.py:491 templates/recruitment/candidate_list.html:276 +msgid "Hiring Source" +msgstr "مصدر التوظيف" + +#: recruitment/models.py:493 +msgid "Public" +msgstr "عام" + +#: recruitment/models.py:494 +msgid "Internal" +msgstr "داخلي" + +#: recruitment/models.py:510 +#: templates/recruitment/agency_assignment_list.html:114 +#: templates/recruitment/agency_portal_dashboard.html:173 +msgid "Candidates" +msgstr "المرشحون" + +#: recruitment/models.py:699 +msgid "Created by" +msgstr "أنشأ بواسطة" + +#: recruitment/models.py:703 +msgid "Training Material" +msgstr "مادة تدريبية" + +#: recruitment/models.py:704 templates/recruitment/training_list.html:4 +#: templates/recruitment/training_list.html:128 +msgid "Training Materials" +msgstr "المواد التدريبية" + +#: recruitment/models.py:712 templates/meetings/list_meetings.html:203 +msgid "Waiting" +msgstr "في الانتظار" + +#: recruitment/models.py:713 templates/meetings/list_meetings.html:204 +msgid "Started" +msgstr "بدأ" + +#: recruitment/models.py:714 templates/meetings/list_meetings.html:205 +msgid "Ended" +msgstr "انتهى" + +#: recruitment/models.py:715 recruitment/models.py:1303 +#: recruitment/models.py:1641 +#: templates/recruitment/agency_assignment_list.html:90 +#: templates/recruitment/agency_portal_dashboard.html:152 +msgid "Cancelled" +msgstr "ملغى" + +#: recruitment/models.py:719 templates/meetings/meeting_details.html:266 +msgid "Meeting ID" +msgstr "معرف الاجتماع" + +#: recruitment/models.py:725 +msgid "Timezone" +msgstr "المنطقة الزمنية" + +#: recruitment/models.py:727 templates/meetings/meeting_details.html:274 +msgid "Join URL" +msgstr "رابط الانضمام" + +#: recruitment/models.py:730 +msgid "Participant Video" +msgstr "فيديو المشارك" + +#: recruitment/models.py:736 +msgid "Join Before Host" +msgstr "الانضمام قبل المضيف" + +#: recruitment/models.py:739 +msgid "Mute Upon Entry" +msgstr "كتم الصوت عند الدخول" + +#: recruitment/models.py:741 +msgid "Waiting Room" +msgstr "غرفة الانتظار" + +#: recruitment/models.py:744 +msgid "Zoom Gateway Response" +msgstr "استجابة بوابة زوم" + +#: recruitment/models.py:778 +#, fuzzy +#| msgid "Meetings" +msgid "Meeting" +msgstr "الاجتماع" + +#: recruitment/models.py:784 +msgid "Author" +msgstr "المؤلف" + +#: recruitment/models.py:793 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Comment" +msgstr "تعليق الاجتماع" + +#: recruitment/models.py:794 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Comments" +msgstr "تعليقات الاجتماع" + +#: recruitment/models.py:1093 +msgid "Source Name" +msgstr "اسم المصدر" + +#: recruitment/models.py:1094 recruitment/models.py:1097 +msgid "e.g., ATS, ERP " +msgstr "مثلاً، ATS, ERP" + +#: recruitment/models.py:1097 templates/meetings/meeting_details.html:307 +msgid "Source Type" +msgstr "نوع المصدر" + +#: recruitment/models.py:1102 +msgid "A description of the source" +msgstr "وصف المصدر" + +#: recruitment/models.py:1107 recruitment/models.py:1247 +#: templates/includes/easy_logs.html:210 +msgid "IP Address" +msgstr "عنوان IP" + +#: recruitment/models.py:1108 +msgid "The IP address of the source" +msgstr "عنوان IP الخاص بالمصدر" + +#: recruitment/models.py:1117 +msgid "API Key" +msgstr "مفتاح واجهة برمجة التطبيقات" + +#: recruitment/models.py:1118 +msgid "API key for authentication (will be encrypted)" +msgstr "مفتاح واجهة برمجة التطبيقات للمصادقة (سيتم تشفيره)" + +#: recruitment/models.py:1124 +msgid "API Secret" +msgstr "سر واجهة برمجة التطبيقات" + +#: recruitment/models.py:1125 +msgid "API secret for authentication (will be encrypted)" +msgstr "سر واجهة برمجة التطبيقات للمصادقة (سيتم تشفيره)" + +#: recruitment/models.py:1130 +msgid "Trusted IP Addresses" +msgstr "عناوين IP الموثوقة" + +#: recruitment/models.py:1131 +msgid "Comma-separated list of trusted IP addresses" +msgstr "قائمة عناوين IP الموثوقة مفصولة بفواصل" + +#: recruitment/models.py:1136 +msgid "Whether this source is active for integration" +msgstr "ما إذا كان هذا المصدر نشطاً للتكامل" + +#: recruitment/models.py:1141 +msgid "Integration Version" +msgstr "إصدار التكامل" + +#: recruitment/models.py:1142 +msgid "Version of the integration protocol" +msgstr "إصدار بروتوكول التكامل" + +#: recruitment/models.py:1147 +msgid "Last Sync At" +msgstr "آخر مزامنة في" + +#: recruitment/models.py:1148 +msgid "Timestamp of the last successful synchronization" +msgstr "الطابع الزمني لآخر مزامنة ناجحة" + +#: recruitment/models.py:1160 +msgid "Sync Status" +msgstr "حالة المزامنة" + +#: recruitment/models.py:1167 +#, fuzzy +#| msgid "Endpoint" +msgid "Sync Endpoint" +msgstr "نقطة نهاية المزامنة" + +#: recruitment/models.py:1168 +msgid "Endpoint URL for sending candidate data (for outbound sync)" +msgstr "عنوان URL لنقطة النهاية لإرسال بيانات المرشح (للمزامنة الصادرة)" + +#: recruitment/models.py:1178 +#, fuzzy +#| msgid "HTTP Method" +msgid "Sync Method" +msgstr "طريقة المزامنة" + +#: recruitment/models.py:1179 +msgid "HTTP method for outbound sync requests" +msgstr "طريقة HTTP لطلبات المزامنة الصادرة" + +#: recruitment/models.py:1189 +#, fuzzy +#| msgid "HTTP Method" +msgid "Test Method" +msgstr "طريقة الاختبار" + +#: recruitment/models.py:1190 +msgid "HTTP method for connection testing" +msgstr "طريقة HTTP لاختبار الاتصال" + +#: recruitment/models.py:1195 +msgid "Custom Headers" +msgstr "رؤوس مخصصة" + +#: recruitment/models.py:1196 +msgid "JSON object with custom HTTP headers for sync requests" +msgstr "كائن JSON يحتوي على رؤوس HTTP مخصصة لطلبات المزامنة" + +#: recruitment/models.py:1200 +msgid "Supports Outbound Sync" +msgstr "يدعم المزامنة الصادرة" + +#: recruitment/models.py:1201 +msgid "Whether this source supports receiving candidate data from ATS" +msgstr "" +"ما إذا كان هذا المصدر يدعم استقبال بيانات المرشح من نظام تتبع المتقدمين" + +#: recruitment/models.py:1208 recruitment/models.py:1230 +#: templates/jobs/job_list.html:269 +msgid "Source" +msgstr "المصدر" + +#: recruitment/models.py:1209 +msgid "Sources" +msgstr "المصادر" + +#: recruitment/models.py:1219 +msgid "Request" +msgstr "الطلب" + +#: recruitment/models.py:1220 +msgid "Response" +msgstr "الاستجابة" + +#: recruitment/models.py:1221 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:98 +msgid "Error" +msgstr "خطأ" + +#: recruitment/models.py:1222 +msgid "Sync" +msgstr "مزامنة" + +#: recruitment/models.py:1223 templates/jobs/job_list.html:395 +msgid "Create Job" +msgstr "إنشاء وظيفة" + +#: recruitment/models.py:1224 +msgid "Update Job" +msgstr "تحديث وظيفة" + +#: recruitment/models.py:1233 templates/includes/easy_logs.html:199 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:25 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:49 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:25 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:49 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:18 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:34 +msgid "Action" +msgstr "الإجراء" + +#: recruitment/models.py:1235 +msgid "Endpoint" +msgstr "نقطة النهاية" + +#: recruitment/models.py:1236 +msgid "HTTP Method" +msgstr "طريقة HTTP" + +#: recruitment/models.py:1238 +msgid "Request Data" +msgstr "بيانات الطلب" + +#: recruitment/models.py:1241 +msgid "Response Data" +msgstr "بيانات الاستجابة" + +#: recruitment/models.py:1244 +msgid "Status Code" +msgstr "رمز الحالة" + +#: recruitment/models.py:1246 +msgid "Error Message" +msgstr "رسالة الخطأ" + +#: recruitment/models.py:1249 +msgid "User Agent" +msgstr "وكيل المستخدم" + +#: recruitment/models.py:1252 +msgid "Processing Time (seconds)" +msgstr "وقت المعالجة (ثواني)" + +#: recruitment/models.py:1260 +msgid "Integration Log" +msgstr "سجل التكامل" + +#: recruitment/models.py:1261 +msgid "Integration Logs" +msgstr "سجلات التكامل" + +#: recruitment/models.py:1283 +msgid "Internal notes about the agency" +msgstr "ملاحظات داخلية حول الوكالة" + +#: recruitment/models.py:1284 +msgid "Select country" +msgstr "اختر الدولة" + +#: recruitment/models.py:1292 templates/recruitment/agency_list.html:4 +#: templates/recruitment/agency_list.html:131 +msgid "Hiring Agencies" +msgstr "وكالات التوظيف" + +#: recruitment/models.py:1301 recruitment/models.py:1642 +#: templates/recruitment/agency_assignment_list.html:89 +#: templates/recruitment/agency_portal_dashboard.html:150 +msgid "Completed" +msgstr "مكتمل" + +#: recruitment/models.py:1302 +#: templates/recruitment/agency_access_link_detail.html:60 +#: templates/recruitment/agency_assignment_detail.html:151 +#: templates/recruitment/agency_assignment_list.html:88 +#: templates/recruitment/agency_assignment_list.html:148 +#: templates/recruitment/agency_portal_assignment_detail.html:165 +#: templates/recruitment/agency_portal_dashboard.html:154 +msgid "Expired" +msgstr "منتهي الصلاحية" + +#: recruitment/models.py:1321 +msgid "Maximum candidates agency can submit for this job" +msgstr "الحد الأقصى للمرشحين الذين يمكن للوكالة تقديمهم لهذه الوظيفة" + +#: recruitment/models.py:1325 +#: templates/recruitment/agency_access_link_detail.html:71 +#, fuzzy +#| msgid "Candidates" +msgid "Candidates Submitted" +msgstr "المرشحون المقدمون" + +#: recruitment/models.py:1326 +msgid "Number of candidates submitted so far" +msgstr "عدد المرشحين المقدمين حتى الآن" + +#: recruitment/models.py:1330 +#: templates/recruitment/agency_portal_assignment_detail.html:410 +#, fuzzy +#| msgid "Applied Date" +msgid "Assigned Date" +msgstr "تاريخ التعيين" + +#: recruitment/models.py:1333 +msgid "Deadline for agency to submit candidates" +msgstr "الموعد النهائي للوكالة لتقديم المرشحين" + +#: recruitment/models.py:1348 +#, fuzzy +#| msgid "Deadline:" +msgid "Deadline Extended" +msgstr "تمديد الموعد النهائي" + +#: recruitment/models.py:1353 +#, fuzzy +#| msgid "Application Deadline" +msgid "Original Deadline" +msgstr "الموعد النهائي الأصلي" + +#: recruitment/models.py:1354 +msgid "Original deadline before extensions" +msgstr "الموعد النهائي الأصلي قبل التمديدات" + +#: recruitment/models.py:1361 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Internal notes about this assignment" +msgstr "ملاحظات داخلية حول هذا التكليف" + +#: recruitment/models.py:1365 +msgid "Agency Job Assignment" +msgstr "تكليف وكالة بالوظيفة" + +#: recruitment/models.py:1366 +msgid "Agency Job Assignments" +msgstr "تكليفات وكالة بالوظائف" + +#: recruitment/models.py:1405 +msgid "Deadline date must be in the future" +msgstr "يجب أن يكون تاريخ الموعد النهائي في المستقبل" + +#: recruitment/models.py:1408 +msgid "Maximum candidates must be greater than 0" +msgstr "يجب أن يكون الحد الأقصى للمرشحين أكبر من 0" + +#: recruitment/models.py:1411 +msgid "Candidates submitted cannot exceed maximum candidates" +msgstr "لا يمكن أن يتجاوز عدد المرشحين المقدمين الحد الأقصى للمرشحين" + +#: recruitment/models.py:1478 +msgid "Unique Token" +msgstr "رمز مميز فريد" + +#: recruitment/models.py:1482 +#, fuzzy +#| msgid "Password" +msgid "Access Password" +msgstr "كلمة مرور الوصول" + +#: recruitment/models.py:1483 +#, fuzzy +#| msgid "Posted successfully!" +msgid "Password for agency access" +msgstr "تم تغيير كلمة المرور بنجاح" + +#: recruitment/models.py:1487 templates/participants/participants_list.html:217 +#: templates/recruitment/agency_access_link_detail.html:51 +msgid "Created At" +msgstr "تم الإنشاء في" + +#: recruitment/models.py:1490 +msgid "When this access link expires" +msgstr "عندما ينتهي رابط الوصول هذا" + +#: recruitment/models.py:1495 +#: templates/recruitment/agency_access_link_detail.html:141 +msgid "Last Accessed" +msgstr "آخر وصول" + +#: recruitment/models.py:1501 +msgid "Access Count" +msgstr "عدد مرات الوصول" + +#: recruitment/models.py:1509 +msgid "Agency Access Link" +msgstr "رابط وصول الوكالة" + +#: recruitment/models.py:1510 +msgid "Agency Access Links" +msgstr "روابط وصول الوكالة" + +#: recruitment/models.py:1524 +msgid "Expiration date must be in the future" +msgstr "يجب أن يكون تاريخ الانتهاء في المستقبل" + +#: recruitment/models.py:1569 recruitment/models.py:1588 +#: templates/interviews/schedule_interviews.html:176 +#: templates/interviews/schedule_interviews.html:205 +msgid "End Time" +msgstr "وقت الانتهاء" + +#: recruitment/models.py:1582 templates/interviews/schedule_interviews.html:146 +msgid "Start Date" +msgstr "تاريخ البدء" + +#: recruitment/models.py:1583 templates/interviews/schedule_interviews.html:153 +msgid "End Date" +msgstr "تاريخ الانتهاء" + +#: recruitment/models.py:1585 templates/interviews/schedule_interviews.html:160 +msgid "Working Days" +msgstr "أيام العمل" + +#: recruitment/models.py:1590 +msgid "Break Start Time" +msgstr "وقت بدء الاستراحة" + +#: recruitment/models.py:1591 +msgid "Break End Time" +msgstr "وقت انتهاء الاستراحة" + +#: recruitment/models.py:1594 +msgid "Interview Duration (minutes)" +msgstr "مدة المقابلة (دقائق)" + +#: recruitment/models.py:1597 +msgid "Buffer Time (minutes)" +msgstr "وقت المخزن المؤقت (دقائق)" + +#: recruitment/models.py:1635 +msgid "Interview Time" +msgstr "وقت المقابلة" + +#: recruitment/models.py:1639 +msgid "Scheduled" +msgstr "مجدول" + +#: recruitment/models.py:1640 +msgid "Confirmed" +msgstr "مؤكد" + +#: recruitment/models.py:1665 templates/recruitment/notification_list.html:49 +msgid "In-App" +msgstr "في التطبيق" + +#: recruitment/models.py:1668 +msgid "Pending" +msgstr "قيد الانتظار" + +#: recruitment/models.py:1669 templates/recruitment/notification_list.html:42 +msgid "Sent" +msgstr "تم الإرسال" + +#: recruitment/models.py:1670 templates/recruitment/notification_list.html:41 +msgid "Read" +msgstr "مقروء" + +#: recruitment/models.py:1672 +msgid "Retrying" +msgstr "إعادة المحاولة" + +#: recruitment/models.py:1678 +msgid "Recipient" +msgstr "المستلم" + +#: recruitment/models.py:1680 +msgid "Notification Message" +msgstr "رسالة الإشعار" + +#: recruitment/models.py:1685 +msgid "Notification Type" +msgstr "نوع الإشعار" + +#: recruitment/models.py:1699 templates/recruitment/notification_detail.html:62 +#, fuzzy +#| msgid "Create Meeting" +msgid "Related Meeting" +msgstr "الاجتماع المرتبط" + +#: recruitment/models.py:1702 +#, fuzzy +#| msgid "Scheduled" +msgid "Scheduled Send Time" +msgstr "وقت الإرسال المجدول" + +#: recruitment/models.py:1703 +msgid "The date and time this notification is scheduled to be sent." +msgstr "التاريخ والوقت المحدد لإرسال هذا الإشعار." + +#: recruitment/models.py:1707 +msgid "Send Attempts" +msgstr "محاولات الإرسال" + +#: recruitment/models.py:1708 +#, fuzzy +#| msgid "Error Message" +msgid "Last Error Message" +msgstr "آخر رسالة خطأ" + +#: recruitment/models.py:1712 +#, fuzzy +#| msgid "Location" +msgid "Notification" +msgstr "الإشعار" + +#: recruitment/models.py:1713 templates/recruitment/notification_list.html:4 +#: templates/recruitment/notification_list.html:12 +#, fuzzy +#| msgid "Location" +msgid "Notifications" +msgstr "الإشعارات" + +#: recruitment/models.py:1737 +#, fuzzy +#| msgid "Participant Video" +msgid "Participant Name" +msgstr "اسم المشارك" + +#: recruitment/models.py:1741 +#: templates/participants/participants_detail.html:184 +#: templates/participants/participants_list.html:216 +msgid "Designation" +msgstr "التصميم" + +#: recruitment/views.py:614 +msgid "Failed to start the job posting process. Please try again." +msgstr "فشل في بدء عملية نشر الوظيفة. يرجى المحاولة مرة أخرى." + +#: recruitment/views.py:2181 templates/includes/easy_logs.html:176 +#, fuzzy +#| msgid "User Agent" +msgid "User Authentication" +msgstr "مصادقة المستخدم" + +#: recruitment/views.py:2184 templates/includes/easy_logs.html:182 +#, fuzzy +#| msgid "Request" +msgid "HTTP Requests" +msgstr "طلبات HTTP" + +#: recruitment/views.py:2187 templates/includes/easy_logs.html:170 +msgid "Model Changes (CRUD)" +msgstr "تغييرات النموذج (CRUD)" + +#: recruitment/views_frontend.py:275 +msgid "You don't have permission to view this page." +msgstr "ليس لديك إذن لعرض هذه الصفحة." + +#: templates/account/account_inactive.html:7 +#: templates/account/account_inactive.html:131 +msgid "Account Inactive" +msgstr "الحساب غير نشط" + +#: templates/account/account_inactive.html:114 +#: templates/account/password_reset_done.html:136 +#: templates/account/password_reset_from_key.html:55 +#: templates/account/password_reset_from_key_done.html:52 +msgid "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" +msgstr "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" + +#: templates/account/account_inactive.html:115 +#: templates/account/password_reset_done.html:137 +#: templates/account/password_reset_from_key.html:56 +#: templates/account/password_reset_from_key_done.html:53 +msgid "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" +msgstr "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" + +#: templates/account/account_inactive.html:116 +#: templates/account/password_reset_done.html:138 +#: templates/account/password_reset_from_key.html:57 +#: templates/account/password_reset_from_key_done.html:54 +msgid "Princess Nourah bint Abdulrahman University" +msgstr "جامعة الأميرة نورة بنت عبدالرحمن" + +#: templates/account/account_inactive.html:117 +#: templates/account/password_reset_done.html:139 +#: templates/account/password_reset_from_key.html:58 +#: templates/account/password_reset_from_key_done.html:55 +#, fuzzy +#| msgid "King Abdullah Academic University Hospital" +msgid "King Abdullah bin Abdulaziz University Hospital" +msgstr "مستشفى الملك عبدالله بن عبدالعزيز الجامعي" + +#: templates/account/account_inactive.html:135 +msgid "" +"Access denied. This account has been marked as inactive by an administrator." +msgstr "تم رفض الوصول. تم وضع علامة على هذا الحساب بأنه غير نشط من قبل مسؤول." + +#: templates/account/account_inactive.html:138 +msgid "" +"If you believe this is an error, please contact the system administrator for " +"assistance." +msgstr "" +"إذا كنت تعتقد أن هذا خطأ، يرجى الاتصال بمسؤول النظام للحصول على المساعدة." + +#: templates/account/account_inactive.html:143 +#: templates/account/password_reset_from_key.html:109 +#, fuzzy +#| msgid "Re-post to LinkedIn" +msgid "Return to Sign In" +msgstr "العودة إلى تسجيل الدخول" + +#: templates/account/email.html:6 templates/account/email.html:33 +#: templates/account/email.html:52 templates/account/logout.html:29 +#, fuzzy +#| msgid "IP Address" +msgid "Email Addresses" +msgstr "عناوين البريد الإلكتروني" + +#: templates/account/email.html:13 templates/account/logout.html:12 +#: templates/user/profile.html:111 +#, fuzzy +#| msgid "Settings" +msgid "Account Settings" +msgstr "إعدادات الحساب" + +#: templates/account/email.html:14 templates/account/logout.html:13 +#: templates/user/profile.html:112 +msgid "Manage your personal details and security." +msgstr "إدارة تفاصيلك الشخصية والأمان." + +#: templates/account/email.html:28 templates/account/logout.html:26 +#: templates/recruitment/agency_portal_submit_candidate.html:182 +#: templates/user/profile.html:123 +#, fuzzy +#| msgid "Internal Information" +msgid "Personal Information" +msgstr "المعلومات الشخصية" + +#: templates/account/email.html:36 templates/account/logout.html:32 +#: templates/account/password_change.html:4 +#: templates/account/password_change.html:15 +#: templates/account/password_change.html:34 +#: templates/account/password_reset_from_key.html:90 +#: templates/user/admin_settings.html:223 templates/user/profile.html:161 +#: templates/user/staff_password_create.html:4 +#: templates/user/staff_password_create.html:16 +#: templates/user/staff_password_create.html:35 +#, fuzzy +#| msgid "Password" +msgid "Change Password" +msgstr "تغيير كلمة المرور" + +#: templates/account/email.html:40 templates/account/logout.html:5 +#: templates/account/logout.html:37 templates/account/logout.html:66 +#: templates/base.html:213 +msgid "Sign Out" +msgstr "تسجيل الخروج" + +#: templates/account/email.html:53 +msgid "" +"These email addresses are linked to your account. You can set the primary " +"address, resend verification, or remove an address." +msgstr "" +"عناوين البريد الإلكتروني هذه مرتبطة بحسابك. يمكنك تعيين العنوان الأساسي أو " +"إعادة إرسال التحقق أو إزالة عنوان." + +#: templates/account/email.html:73 +msgid "Primary" +msgstr "أساسي" + +#: templates/account/email.html:76 +msgid "Verified" +msgstr "موثق" + +#: templates/account/email.html:78 +msgid "Unverified" +msgstr "غير موثق" + +#: templates/account/email.html:89 +msgid "Make Primary" +msgstr "جعله أساسيًا" + +#: templates/account/email.html:98 +#, fuzzy +#| msgid "Required Qualifications" +msgid "Re-send Verification" +msgstr "إعادة إرسال التحقق" + +#: templates/account/email.html:107 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_delete.html:4 +msgid "Remove" +msgstr "إزالة" + +#: templates/account/email.html:114 +#, fuzzy +#| msgid "No candidates found." +msgid "No email addresses found." +msgstr "لم يتم العثور على عناوين بريد إلكتروني." + +#: templates/account/email.html:121 +msgid "Add Email Address" +msgstr "إضافة عنوان بريد إلكتروني" + +#: templates/account/email.html:136 +#, fuzzy +#| msgid "Email" +msgid "Add Email" +msgstr "إضافة بريد إلكتروني" + +#: templates/account/email/email_confirmation_message.html:5 +#: templates/account/email/email_confirmation_message.txt:4 +#: templates/account/email/password_reset_key_message.html:14 +#: templates/account/email/password_reset_key_message.txt:7 +msgid "Hello," +msgstr "مرحباً،" + +#: templates/account/email/email_confirmation_message.html:9 +#: templates/account/email/email_confirmation_message.txt:6 +msgid "" +"To verify the ownership of your email address, please click the confirmation " +"link below:" +msgstr "" +"للتحقق من ملكية عنوان بريدك الإلكتروني، يرجى النقر على رابط التأكيد أدناه:" + +#: templates/account/email/email_confirmation_message.html:15 +#: templates/account/email/email_confirmation_message.txt:9 +msgid "Confirm My KAAUH ATS Email" +msgstr "تأكيد بريدي الإلكتروني في نظام توظيف مستشفى جامعة نورة" + +#: templates/account/email/email_confirmation_message.html:20 +#: templates/account/email/email_confirmation_message.txt:13 +msgid "" +"If you did not request this verification, you can safely ignore this email." +msgstr "إذا لم تطلب هذا التحقق، يمكنك تجاهل هذا البريد الإلكتروني بأمان." + +#: templates/account/email/email_confirmation_message.html:24 +#: templates/account/email/email_confirmation_message.txt:15 +msgid "Alternatively, copy and paste this link into your browser:" +msgstr "بدلاً من ذلك، انسخ والصق هذا الرابط في متصفحك:" + +#: templates/account/email/password_reset_key_message.html:10 +#: templates/account/email/password_reset_key_message.txt:5 +msgid "Password Reset Request" +msgstr "طلب إعادة تعيين كلمة المرور" + +#: templates/account/email/password_reset_key_message.html:16 +#: templates/account/email/password_reset_key_message.txt:9 +msgid "" +"You are receiving this email because you or someone else has requested a " +"password reset for your account at" +msgstr "" +"تتلقى هذا البريد الإلكتروني لأنك أو شخص آخر طلب إعادة تعيين كلمة المرور " +"لحسابك في" + +#: templates/account/email/password_reset_key_message.html:21 +#: templates/account/email/password_reset_key_message.txt:12 +msgid "Click Here to Reset Your Password" +msgstr "اضغط هنا لإعادة تعيين كلمة المرور الخاصة بك" + +#: templates/account/email/password_reset_key_message.html:25 +#: templates/account/email/password_reset_key_message.txt:16 +msgid "This link is only valid for a limited time." +msgstr "هذا الرابط صالح لفترة محدودة فقط." + +#: templates/account/email/password_reset_key_message.html:27 +#: templates/account/email/password_reset_key_message.txt:18 +msgid "" +"If you did not request a password reset, please ignore this email. Your " +"password will remain unchanged." +msgstr "" +"إذا لم تطلب إعادة تعيين كلمة المرور، يرجى تجاهل هذا البريد الإلكتروني. ستبقى " +"كلمة المرور الخاصة بك دون تغيير." + +#: templates/account/email/password_reset_key_message.html:30 +#: templates/account/email/password_reset_key_message.txt:20 +msgid "Thank you," +msgstr "شكراً لك،" + +#: templates/account/email/password_reset_key_message.html:31 +#: templates/account/email/password_reset_key_message.txt:21 +msgid "KAAUH ATS Team" +msgstr "فريق نظام توظيف مستشفى جامعة نورة" + +#: templates/account/email/password_reset_key_message.html:36 +#: templates/account/email/password_reset_key_message.txt:24 +msgid "" +"If the button above does not work, copy and paste the following link into " +"your browser:" +msgstr "إذا لم يعمل الزر أعلاه، انسخ والصق الرابط التالي في متصفحك:" + +#: templates/account/email_confirm.html:5 +msgid "Confirm Email Address" +msgstr "تأكيد عنوان البريد الإلكتروني" + +#: templates/account/email_confirm.html:13 +msgid "Account Verification" +msgstr "التحقق من الحساب" + +#: templates/account/email_confirm.html:14 +msgid "Verify your email to secure your account and unlock full features." +msgstr "تحقق من بريدك الإلكتروني لتأمين حسابك وفتح الميزات الكاملة." + +#: templates/account/email_confirm.html:32 +msgid "Confirm Your Email Address" +msgstr "تأكيد عنوان بريدك الإلكتروني" + +#: templates/account/email_confirm.html:35 +#, python-format +msgid "" +"Please confirm that **%(email)s** is the correct email address for your " +"account." +msgstr "" +"الرجاء التأكد من أن **%(email)s** هو عنوان البريد الإلكتروني الصحيح لحسابك." + +#: templates/account/email_confirm.html:44 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm & Activate" +msgstr "تأكيد وتفعيل" + +#: templates/account/email_confirm.html:53 +msgid "Verification Failed" +msgstr "فشل التحقق" + +#: templates/account/email_confirm.html:56 +msgid "The email confirmation link is expired or invalid." +msgstr "رابط تأكيد البريد الإلكتروني منتهي الصلاحية أو غير صالح." + +#: templates/account/email_confirm.html:59 +msgid "" +"If you recently requested a link, please ensure you use the newest one. You " +"can request a new verification email from your account settings." +msgstr "" +"إذا طلبت رابطًا مؤخرًا، يرجى التأكد من استخدام أحدث رابط. يمكنك طلب بريد " +"إلكتروني جديد للتحقق من إعدادات حسابك." + +#: templates/account/email_confirm.html:63 +#, fuzzy +#| msgid "Settings" +msgid "Go to Settings" +msgstr "انتقال إلى الإعدادات" + +#: templates/account/login.html:151 templates/account/login.html:178 +#, fuzzy +#| msgid "Sign out" +msgid "Sign In" +msgstr "تسجيل الدخول" + +#: templates/account/login.html:158 +#, fuzzy +#| msgid "Email" +msgid "Email *" +msgstr "البريد الإلكتروني *" + +#: templates/account/login.html:159 +#, fuzzy +#| msgid "Enter email" +msgid "Enter your email" +msgstr "أدخل بريدك الإلكتروني" + +#: templates/account/login.html:163 +#, fuzzy +#| msgid "Password" +msgid "Password *" +msgstr "كلمة المرور *" + +#: templates/account/login.html:167 templates/account/password_reset.html:150 +#, fuzzy +#| msgid "Password" +msgid "Forgot Password?" +msgstr "هل نسيت كلمة المرور؟" + +#: templates/account/login.html:174 +msgid "Keep me signed in" +msgstr "اجعلني مسجلاً للدخول" + +#: templates/account/logout.html:50 +#, fuzzy +#| msgid "Sign Out" +msgid "Confirm Sign Out" +msgstr "تأكيد تسجيل الخروج" + +#: templates/account/logout.html:52 +#, fuzzy +#| msgid "Are you sure you want to sign out?" +msgid "Are you sure you want to sign out of your account?" +msgstr "هل أنت متأكد أنك تريد تسجيل الخروج من حسابك؟" + +#: templates/account/logout.html:71 +#: templates/forms/form_templates_list.html:382 +#: templates/includes/email_compose_form.html:110 +#: templates/includes/meeting_form.html:40 +#: templates/interviews/schedule_interviews.html:221 +#: templates/jobs/create_job.html:327 templates/jobs/edit_job.html:327 +#: templates/jobs/job_detail.html:536 +#: templates/meetings/create_meeting.html:184 +#: templates/meetings/delete_meeting_form.html:8 +#: templates/meetings/meeting_details.html:399 +#: templates/meetings/set_candidate_form.html:6 +#: templates/meetings/update_meeting.html:237 +#: templates/participants/participants_detail.html:252 +#: templates/participants/participants_list.html:334 +#: templates/recruitment/agency_access_link_form.html:127 +#: templates/recruitment/agency_assignment_detail.html:430 +#: templates/recruitment/agency_assignment_form.html:213 +#: templates/recruitment/agency_confirm_delete.html:357 +#: templates/recruitment/agency_form.html:337 +#: templates/recruitment/agency_portal_assignment_detail.html:510 +#: templates/recruitment/agency_portal_assignment_detail.html:575 +#: templates/recruitment/agency_portal_assignment_detail.html:609 +#: templates/recruitment/agency_portal_submit_candidate.html:333 +#: templates/recruitment/notification_confirm_all_read.html:53 +#: templates/recruitment/notification_confirm_delete.html:33 +#: templates/recruitment/schedule_meeting_form.html:89 +msgid "Cancel" +msgstr "إلغاء" + +#: templates/account/password_change.html:19 +#: templates/user/staff_password_create.html:20 +msgid "" +"Please enter your current password and a new password to secure your account." +msgstr "الرجاء إدخال كلمة المرور الحالية وكلمة مرور جديدة لتأمين حسابك." + +#: templates/account/password_change.html:41 +msgid "Return to Profile" +msgstr "العودة إلى الملف الشخصي" + +#: templates/account/password_reset.html:154 +msgid "Enter your e-mail address to reset your password." +msgstr "أدخل عنوان بريدك الإلكتروني لإعادة تعيين كلمة المرور الخاصة بك." + +#: templates/account/password_reset.html:162 +#, fuzzy +#| msgid "IP Address" +msgid "E-mail Address" +msgstr "عنوان البريد الإلكتروني" + +#: templates/account/password_reset.html:179 +#, fuzzy +#| msgid "Password" +msgid "Reset My Password" +msgstr "إعادة تعيين كلمة المرور الخاصة بي" + +#: templates/account/password_reset.html:185 +msgid "Remember your password?" +msgstr "هل تتذكر كلمة المرور الخاصة بك؟" + +#: templates/account/password_reset.html:186 +msgid "Log In" +msgstr "تسجيل الدخول" + +#: templates/account/password_reset_done.html:7 +#: templates/account/password_reset_done.html:151 +msgid "Password Reset Sent" +msgstr "تم إرسال إعادة تعيين كلمة المرور" + +#: templates/account/password_reset_done.html:160 +#, fuzzy +#| msgid "" +#| "\n" +#| "                        We've **sent an email** to the address you " +#| "provided with instructions on how to reset your password.\n" +#| "                        " +msgid "" +"\n" +" We've **sent an email** to the address you provided " +"with instructions on how to reset your password.\n" +" " +msgstr "" +"\n" +"لقد **أرسلنا بريدًا إلكترونيًا** إلى العنوان الذي قدمته مع تعليمات حول كيفية " +"إعادة تعيين كلمة المرور الخاصة بك." + +#: templates/account/password_reset_done.html:168 +msgid "" +"Please check your inbox (and spam folder). The link in the email is " +"temporary and will expire soon for security reasons." +msgstr "" +"يرجى التحقق من صندوق الوارد الخاص بك (ومجلد الرسائل غير المرغوب فيها). " +"الرابط الموجود في البريد الإلكتروني مؤقت وسينتهي قريبًا لأسباب أمنية." + +#: templates/account/password_reset_done.html:175 +msgid "Return to Login" +msgstr "العودة إلى تسجيل الدخول" + +#: templates/account/password_reset_from_key.html:7 +#: templates/account/password_reset_from_key.html:70 +#, fuzzy +#| msgid "Password" +msgid "Set New Password" +msgstr "تعيين كلمة مرور جديدة" + +#: templates/account/password_reset_from_key.html:76 +msgid "Please enter your new password below." +msgstr "يرجى إدخال كلمة المرور الجديدة الخاصة بك أدناه." + +#: templates/account/password_reset_from_key.html:79 +msgid "You can then log in." +msgstr "يمكنك بعد ذلك تسجيل الدخول." + +#: templates/account/password_reset_from_key.html:96 +msgid "Password Reset Failed" +msgstr "فشل إعادة تعيين كلمة المرور" + +#: templates/account/password_reset_from_key.html:98 +msgid "The password reset link is invalid or has expired." +msgstr "رابط إعادة تعيين كلمة المرور غير صالح أو منتهي الصلاحية." + +#: templates/account/password_reset_from_key.html:102 +msgid "Request New Reset Link" +msgstr "طلب رابط إعادة تعيين جديد" + +#: templates/account/password_reset_from_key_done.html:7 +#, fuzzy +#| msgid "Password" +msgid "Password Changed" +msgstr "تم تغيير كلمة المرور" + +#: templates/account/password_reset_from_key_done.html:69 +#, fuzzy +#| msgid "Posted successfully!" +msgid "Password Changed Successfully" +msgstr "تم تغيير كلمة المرور بنجاح" + +#: templates/account/password_reset_from_key_done.html:72 +msgid "" +"Your password has been set. You can now use your new password to sign in." +msgstr "" +"تم تعيين كلمة المرور الخاصة بك. يمكنك الآن استخدام كلمة المرور الجديدة " +"لتسجيل الدخول." + +#: templates/account/password_reset_from_key_done.html:77 +#: templates/account/verification_sent.html:182 +#, fuzzy +#| msgid "Post to LinkedIn" +msgid "Go to Sign In" +msgstr "انتقال إلى تسجيل الدخول" + +#: templates/account/verification_sent.html:153 +msgid "Verify Your Email Address" +msgstr "تحقق من عنوان بريدك الإلكتروني" + +#: templates/account/verification_sent.html:159 +#, fuzzy +#| msgid "" +#| "\n" +#| "                        We have sent an email to your email id for " +#| "verification. Follow the link provided to finalize the signup process.\n" +#| "                       " +msgid "" +"\n" +" We have sent an email to your email id for " +"verification. Follow the link provided to finalize the signup process.\n" +" " +msgstr "" +"\n" +"لقد أرسلنا بريدًا إلكترونيًا إلى معرف بريدك الإلكتروني للتحقق. اتبع الرابط " +"المقدم لإنهاء عملية التسجيل." + +#: templates/account/verification_sent.html:165 +msgid "" +"If you do not see the verification email in your main inbox, please check " +"your spam folder." +msgstr "" +"إذا لم ترَ رسالة التحقق الإلكترونية في صندوق الوارد الرئيسي، يرجى التحقق من " +"مجلد الرسائل غير المرغوب فيها." + +#: templates/account/verification_sent.html:169 +msgid "" +"Please contact us if you do not receive the verification email within a few " +"minutes." +msgstr "" +"يرجى الاتصال بنا إذا لم تستلم رسالة التحقق الإلكترونية في غضون بضع دقائق." + +#: templates/account/verification_sent.html:176 +msgid "Change or Resend Email" +msgstr "تغيير أو إعادة إرسال البريد الإلكتروني" + +#: templates/admin/sync_dashboard.html:4 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_index.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/base_site.html:3 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/index.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:15 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/layouts/base.html:7 +msgid "Django site admin" +msgstr "إدارة موقع Django" + +#: templates/agency_base.html:9 +#, fuzzy +#| msgid "King Abdullah Academic University Hospital" +msgid "King Abdullah Academic University Hospital - Agency Portal" +msgstr "مستشفى الملك عبدالله الجامعي - بوابة الوكالة" + +#: templates/agency_base.html:10 +msgid "KAAUH Agency Portal" +msgstr "بوابة وكالة مستشفى الملك عبدالله الجامعي" + +#: templates/agency_base.html:31 templates/base.html:38 +msgid "Saudi Vision 2030" +msgstr "رؤية السعودية 2030" + +#: templates/agency_base.html:59 templates/base.html:59 templates/base.html:63 +msgid "kaauh logo green bg" +msgstr "شعار مستشفى الملك عبدالله الجامعي بخلفية خضراء" + +#: templates/agency_base.html:60 templates/agency_base.html:137 +#: templates/recruitment/agency_portal_login.html:126 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency Portal" +msgstr "بوابة الوكالة" + +#: templates/agency_base.html:64 templates/base.html:68 +msgid "Toggle navigation" +msgstr "تبديل التنقل" + +#: templates/agency_base.html:75 templates/base.html:77 +#: templates/forms/partials/candidate_facing_base.html:267 +msgid "Toggle language menu" +msgstr "تبديل قائمة اللغة" + +#: templates/agency_base.html:103 templates/includes/easy_logs.html:261 +msgid "Logout" +msgstr "تسجيل الخروج" + +#: templates/agency_base.html:119 templates/base.html:302 +#: templates/jobs/job_detail.html:520 +#: templates/recruitment/candidate_exam_view.html:355 +#: templates/recruitment/candidate_hired_view.html:344 +#: templates/recruitment/candidate_interview_view.html:508 +#: templates/recruitment/candidate_screening_view.html:495 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:19 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:52 +msgid "Close" +msgstr "إغلاق" + +#: templates/agency_base.html:133 templates/base.html:315 +msgid "King Abdullah Academic University Hospital (KAAUH)." +msgstr "مستشفى الملك عبدالله الجامعي (KAAUH)." + +#: templates/agency_base.html:134 templates/base.html:316 +msgid "All rights reserved." +msgstr "جميع الحقوق محفوظة." + +#: templates/agency_base.html:175 +#, fuzzy +#| msgid "Are you sure you want to sign out?" +msgid "Are you sure you want to logout?" +msgstr "هل أنت متأكد من رغبتك في تسجيل الخروج؟" + +#: templates/base.html:9 +msgid "King Abdullah Academic University Hospital - Applicant Tracking System" +msgstr "مستشفى الملك عبدالله الجامعي - نظام تتبع المتقدمين" + +#: templates/base.html:10 +msgid "University ATS" +msgstr "نظام تتبع المتقدمين الجامعي" + +#: templates/base.html:134 +msgid "Toggle user menu" +msgstr "تبديل قائمة المستخدم" + +#: templates/base.html:141 templates/base.html:143 templates/base.html:159 +msgid "Your account" +msgstr "حسابك" + +#: templates/base.html:175 +msgid "My Profile" +msgstr "ملفي الشخصي" + +#: templates/base.html:179 +msgid "Settings" +msgstr "الإعدادات" + +#: templates/base.html:180 +msgid "Activity Log" +msgstr "سجل النشاط" + +#: templates/base.html:192 +msgid "Connect LinkedIn" +msgstr "ربط LinkedIn" + +#: templates/base.html:198 +msgid "LinkedIn Connected" +msgstr "LinkedIn متصل" + +#: templates/base.html:210 +msgid "Sign out" +msgstr "تسجيل الخروج" + +#: templates/base.html:232 templates/jobs/job_candidates_list.html:123 +#: templates/recruitment/partials/ai_overview_breadcromb.html:49 +msgid "Jobs" +msgstr "الوظائف" + +#: templates/base.html:240 templates/jobs/job_candidates_list.html:125 +#: templates/jobs/job_candidates_list.html:207 +#: templates/jobs/job_detail.html:291 templates/jobs/job_list.html:354 +#: templates/recruitment/partials/ai_overview_breadcromb.html:71 +msgid "Applicants" +msgstr "المتقدمون" + +#: templates/base.html:248 +msgid "Agencies" +msgstr "الوكالات" + +#: templates/base.html:258 +msgid "Meetings" +msgstr "الاجتماعات" + +#: templates/base.html:270 +#: templates/recruitment/candidate_interview_view.html:475 +#: templates/recruitment/candidate_interview_view.html:495 +msgid "Participants" +msgstr "المشاركون" + +#: templates/base.html:320 +#, fuzzy +#| msgid "Created by" +msgid "Powered by" +msgstr "بدعم من" + +#: templates/base.html:361 +msgid "Are you sure you want to sign out?" +msgstr "هل أنت متأكد من رغبتك في تسجيل الخروج؟" + +#: templates/forms/application_detail.html:7 +#: templates/forms/application_detail.html:61 +msgid "Job Overview" +msgstr "نظرة عامة على الوظيفة" + +#: templates/forms/application_detail.html:39 +msgid "Ready to Apply?" +msgstr "جاهز للتقديم؟" + +#: templates/forms/application_detail.html:42 +msgid "Review the job details, then apply below." +msgstr "راجع تفاصيل الوظيفة، ثم قدم طلبك أدناه." + +#: templates/forms/application_detail.html:46 +#: templates/forms/application_detail.html:106 +msgid "Apply for this Position" +msgstr "التقدم لهذه الوظيفة" + +#: templates/forms/application_detail.html:66 +#: templates/jobs/job_detail.html:240 +msgid "Salary:" +msgstr "الراتب:" + +#: templates/forms/application_detail.html:73 +#: templates/jobs/job_detail.html:177 +#: templates/recruitment/agency_portal_submit_candidate.html:142 +msgid "Deadline:" +msgstr "الموعد النهائي:" + +#: templates/forms/application_detail.html:77 +msgid "EXPIRED" +msgstr "منتهي الصلاحية" + +#: templates/forms/application_detail.html:80 +msgid "Not specified" +msgstr "غير محدد" + +#: templates/forms/application_detail.html:84 +#: templates/jobs/job_candidates_list.html:149 +#: templates/jobs/job_detail.html:231 +msgid "Job Type:" +msgstr "نوع الوظيفة:" + +#: templates/forms/application_detail.html:85 +#: templates/jobs/job_candidates_list.html:146 +#: templates/jobs/job_detail.html:237 +msgid "Location:" +msgstr "الموقع:" + +#: templates/forms/application_detail.html:86 +#: templates/jobs/job_candidates_list.html:143 +#: templates/jobs/job_detail.html:225 +#: templates/recruitment/agency_portal_submit_candidate.html:139 +msgid "Department:" +msgstr "القسم:" + +#: templates/forms/application_detail.html:87 +msgid "JOB ID:" +msgstr "معرف الوظيفة:" + +#: templates/forms/application_detail.html:88 +#: templates/jobs/job_candidates_list.html:152 +#: templates/jobs/job_detail.html:234 +msgid "Workplace:" +msgstr "مكان العمل:" + +#: templates/forms/application_detail.html:91 +#: templates/jobs/create_job.html:189 templates/jobs/edit_job.html:189 +#: templates/jobs/job_detail.html:256 +msgid "Job Description" +msgstr "وصف الوظيفة" + +#: templates/forms/application_detail.html:92 +#, fuzzy +#| msgid "Required Qualifications" +msgid "Qualifications" +msgstr "المؤهلات" + +#: templates/forms/application_detail.html:93 +#: templates/jobs/create_job.html:221 templates/jobs/edit_job.html:221 +#: templates/jobs/job_detail.html:268 +msgid "Benefits" +msgstr "المزايا" + +#: templates/forms/application_detail.html:94 +#: templates/jobs/create_job.html:229 templates/jobs/edit_job.html:229 +#: templates/jobs/job_detail.html:274 +msgid "Application Instructions" +msgstr "تعليمات التقديم" + +#: templates/forms/application_submit_form.html:489 +#: templates/forms/partials/candidate_facing_base.html:11 +msgid "Application Form" +msgstr "نموذج التقديم" + +#: templates/forms/application_submit_form.html:504 +msgid "Review Your Application" +msgstr "مراجعة طلبك" + +#: templates/forms/application_submit_form.html:516 +msgid "Back" +msgstr "العودة" + +#: templates/forms/application_submit_form.html:520 +#: templates/includes/paginator.html:20 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/pagination_infinite.html:9 +msgid "Next" +msgstr "التالي" + +#: templates/forms/application_submit_form.html:529 +msgid "Submit Application" +msgstr "إرسال الطلب" + +#: templates/forms/form_submission_details.html:160 +#, fuzzy +#| msgid "Job Details" +msgid "Submission Details" +msgstr "تفاصيل الإرسال" + +#: templates/forms/form_submission_details.html:162 +#: templates/forms/form_template_all_submissions.html:249 +#: templates/forms/form_template_all_submissions.html:364 +#, fuzzy +#| msgid "Back to Jobs" +msgid "Back to Submissions" +msgstr "العودة إلى الإرسالات" + +#: templates/forms/form_submission_details.html:170 +msgid "Submission Metadata" +msgstr "بيانات تعريف الإرسال" + +#: templates/forms/form_submission_details.html:176 +msgid "Submission ID:" +msgstr "معرف الإرسال:" + +#: templates/forms/form_submission_details.html:180 +#: templates/recruitment/agency_portal_submit_candidate.html:151 +#, fuzzy +#| msgid "Submit" +msgid "Submitted:" +msgstr "تاريخ الإرسال:" + +#: templates/forms/form_submission_details.html:184 +msgid "Form:" +msgstr "النموذج:" + +#: templates/forms/form_submission_details.html:192 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Name:" +msgstr "اسم المتقدم:" + +#: templates/forms/form_submission_details.html:198 +#, fuzzy +#| msgid "Email" +msgid "Email:" +msgstr "البريد الإلكتروني:" + +#: templates/forms/form_submission_details.html:208 +#, fuzzy +#| msgid "Response" +msgid "Form Responses" +msgstr "استجابات النموذج" + +#: templates/forms/form_submission_details.html:219 +msgid "Field Property" +msgstr "خاصية الحقل" + +#: templates/forms/form_submission_details.html:227 +#, fuzzy +#| msgid "Response Data" +msgid "Response Value" +msgstr "قيمة الاستجابة" + +#: templates/forms/form_submission_details.html:233 +#, fuzzy +#| msgid "Download Resume" +msgid "Download File" +msgstr "تحميل الملف" + +#: templates/forms/form_submission_details.html:234 +#, fuzzy +#| msgid "Download Resume" +msgid "Download" +msgstr "تحميل" + +#: templates/forms/form_submission_details.html:250 +#, fuzzy +#| msgid "No description provided" +msgid "Not provided" +msgstr "لم يتم تقديمه" + +#: templates/forms/form_submission_details.html:256 +#, fuzzy +#| msgid "Application Stage" +msgid "Associated Stage" +msgstr "المرحلة المرتبطة" + +#: templates/forms/form_submission_details.html:264 +msgid "Field Required" +msgstr "الحقل مطلوب" + +#: templates/forms/form_submission_details.html:268 +#: templates/recruitment/candidate_detail.html:538 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:867 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:88 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:96 +msgid "Yes" +msgstr "نعم" + +#: templates/forms/form_submission_details.html:270 +#: templates/recruitment/candidate_detail.html:540 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:868 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:89 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:97 +msgid "No" +msgstr "لا" + +#: templates/forms/form_submission_details.html:281 +msgid "No response fields were found for this submission." +msgstr "لم يتم العثور على حقول استجابة لهذا الإرسال." + +#: templates/forms/form_submission_details.html:282 +msgid "" +"This may occur if the form template was modified or responses were cleared." +msgstr "قد يحدث هذا إذا تم تعديل قالب النموذج أو مسح الاستجابات." + +#: templates/forms/form_template_all_submissions.html:232 +#: templates/forms/form_template_submissions_list.html:188 +#: templates/jobs/job_candidates_list.html:122 +#: templates/recruitment/agency_portal_submit_candidate.html:95 +msgid "Dashboard" +msgstr "لوحة التحكم" + +#: templates/forms/form_template_all_submissions.html:233 +#: templates/forms/form_template_submissions_list.html:189 +#: templates/forms/form_templates_list.html:152 +msgid "Form Templates" +msgstr "قوالب النماذج" + +#: templates/forms/form_template_all_submissions.html:234 +#: templates/forms/form_template_submissions_list.html:193 +#: templates/forms/form_templates_list.html:240 +#: templates/forms/form_templates_list.html:295 +#: templates/jobs/job_list.html:320 templates/jobs/job_list.html:373 +#: templates/recruitment/agency_access_link_detail.html:151 +msgid "Submissions" +msgstr "الإرسالات" + +#: templates/forms/form_template_all_submissions.html:235 +msgid "All Submissions Table" +msgstr "جدول جميع الإرسالات" + +#: templates/forms/form_template_all_submissions.html:244 +msgid "All Submissions for" +msgstr "جميع الإرسالات لـ" + +#: templates/forms/form_template_all_submissions.html:258 +#: templates/forms/form_template_submissions_list.html:227 +msgid "Submission ID" +msgstr "معرف الإرسال" + +#: templates/forms/form_template_all_submissions.html:259 +#: templates/forms/form_template_submissions_list.html:228 +#: templates/forms/form_template_submissions_list.html:265 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Name" +msgstr "اسم المتقدم" + +#: templates/forms/form_template_all_submissions.html:260 +#: templates/forms/form_template_submissions_list.html:229 +#: templates/forms/form_template_submissions_list.html:266 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Email" +msgstr "البريد الإلكتروني للمتقدم" + +#: templates/forms/form_template_all_submissions.html:261 +#: templates/forms/form_template_submissions_list.html:230 +#: templates/forms/form_template_submissions_list.html:267 +#, fuzzy +#| msgid "Submitted by Agency" +msgid "Submitted At" +msgstr "تاريخ الإرسال" + +#: templates/forms/form_template_all_submissions.html:315 +#: templates/forms/form_template_submissions_list.html:286 +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| "                                Showing %(start)s to %(end)s of %(total)s " +#| "results.\n" +#| "                            " +msgid "" +"\n" +" Showing %(start)s to %(end)s of %(total)s " +"results.\n" +" " +msgstr "" +"\n" +"                                عرض %(start)s إلى %(end)s من %(total)s نتيجة." + +#: templates/forms/form_template_all_submissions.html:336 +#: templates/forms/form_template_submissions_list.html:307 +#: templates/includes/paginator.html:15 +msgid "Page" +msgstr "صفحة" + +#: templates/forms/form_template_all_submissions.html:336 +#: templates/forms/form_template_submissions_list.html:307 +#: templates/includes/easy_logs.html:159 +msgid "of" +msgstr "من" + +#: templates/forms/form_template_all_submissions.html:359 +#: templates/forms/form_template_submissions_list.html:330 +#, fuzzy +#| msgid "No meetings found." +msgid "No Submissions Found" +msgstr "لم يتم العثور على إرسالات" + +#: templates/forms/form_template_all_submissions.html:361 +#: templates/forms/form_template_submissions_list.html:332 +msgid "There are no submissions for this form template yet." +msgstr "لا توجد إرسالات لقالب النموذج هذا بعد." + +#: templates/forms/form_template_submissions_list.html:202 +msgid "Submissions for" +msgstr "الإرسالات لـ" + +#: templates/forms/form_template_submissions_list.html:208 +#, fuzzy +#| msgid "View All Applicants" +msgid "View All in Table" +msgstr "عرض الكل في جدول" + +#: templates/forms/form_template_submissions_list.html:211 +#: templates/forms/form_template_submissions_list.html:335 +msgid "Back to Templates" +msgstr "العودة إلى القوالب" + +#: templates/forms/form_template_submissions_list.html:231 +#: templates/forms/form_templates_list.html:275 +#: templates/jobs/job_candidates_list.html:231 templates/jobs/job_list.html:272 +#: templates/meetings/list_meetings.html:313 +#: templates/participants/participants_list.html:218 +#: templates/recruitment/agency_access_link_detail.html:168 +#: templates/recruitment/agency_assignment_detail.html:243 +#: templates/recruitment/agency_assignment_detail.html:339 +#: templates/recruitment/agency_assignment_list.html:117 +#: templates/recruitment/agency_list.html:191 +#: templates/recruitment/agency_portal_assignment_detail.html:245 +#: templates/recruitment/candidate_exam_view.html:255 +#: templates/recruitment/candidate_hired_view.html:237 +#: templates/recruitment/candidate_interview_view.html:269 +#: templates/recruitment/candidate_list.html:278 +#: templates/recruitment/candidate_offer_view.html:249 +#: templates/recruitment/candidate_screening_view.html:382 +#: templates/recruitment/notification_detail.html:126 +#: templates/recruitment/partials/_candidate_table.html:14 +#: templates/recruitment/training_list.html:207 +#: templates/user/admin_settings.html:179 +msgid "Actions" +msgstr "الإجراءات" + +#: templates/forms/form_template_submissions_list.html:243 +#: templates/forms/form_template_submissions_list.html:272 +#: templates/recruitment/agency_assignment_detail.html:268 +#: templates/recruitment/agency_assignment_list.html:160 +#: templates/recruitment/agency_portal_dashboard.html:209 +#, fuzzy +#| msgid "Core Details" +msgid "View Details" +msgstr "عرض التفاصيل" + +#: templates/forms/form_template_submissions_list.html:260 +#, fuzzy +#| msgid "Submit" +msgid "Submission" +msgstr "الإرسال" + +#: templates/forms/form_templates_list.html:155 +#, fuzzy +#| msgid "Create Template" +msgid "Create New Template" +msgstr "إنشاء قالب جديد" + +#: templates/forms/form_templates_list.html:165 +#, fuzzy +#| msgid "Template Name" +msgid "Search by Template Name" +msgstr "البحث باسم القالب" + +#: templates/forms/form_templates_list.html:169 +msgid "Search templates by name..." +msgstr "البحث عن القوالب بالاسم..." + +#: templates/forms/form_templates_list.html:177 +#: templates/includes/search_form.html:16 +#: templates/recruitment/agency_assignment_list.html:77 +#: templates/recruitment/agency_assignment_list.html:98 +#: templates/recruitment/agency_list.html:167 +msgid "Search" +msgstr "بحث" + +#: templates/forms/form_templates_list.html:183 +#, fuzzy +#| msgid "Search" +msgid "Clear Search" +msgstr "مسح البحث" + +#: templates/forms/form_templates_list.html:213 +#: templates/forms/form_templates_list.html:271 +msgid "Stages" +msgstr "المراحل" + +#: templates/forms/form_templates_list.html:217 +#: templates/forms/form_templates_list.html:272 +msgid "Fields" +msgstr "الحقول" + +#: templates/forms/form_templates_list.html:226 +msgid "No description provided" +msgstr "لم يتم تقديم وصف" + +#: templates/forms/form_templates_list.html:234 +#: templates/forms/form_templates_list.html:289 +#: templates/jobs/job_list.html:314 +msgid "Preview" +msgstr "معاينة" + +#: templates/forms/form_templates_list.html:237 +#: templates/forms/form_templates_list.html:292 +#: templates/jobs/job_candidates_list.html:257 +#: templates/jobs/job_candidates_list.html:353 templates/jobs/job_list.html:306 +#: templates/jobs/job_list.html:317 +#: templates/participants/participants_list.html:236 +#: templates/participants/participants_list.html:280 +#: templates/recruitment/agency_assignment_list.html:164 +#: templates/recruitment/agency_list.html:251 +#: templates/recruitment/agency_list.html:323 +#: templates/recruitment/candidate_list.html:329 +#: templates/recruitment/candidate_list.html:377 +#: templates/recruitment/training_list.html:181 +#: templates/recruitment/training_list.html:222 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:51 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:51 +msgid "Edit" +msgstr "تعديل" + +#: templates/forms/form_templates_list.html:243 +#: templates/forms/form_templates_list.html:298 +#: templates/jobs/job_candidates_list.html:260 +#: templates/jobs/job_candidates_list.html:356 +#: templates/meetings/list_meetings.html:284 +#: templates/meetings/list_meetings.html:372 +#: templates/participants/participants_detail.html:142 +#: templates/participants/participants_detail.html:255 +#: templates/participants/participants_list.html:239 +#: templates/participants/participants_list.html:282 +#: templates/participants/participants_list.html:337 +#: templates/recruitment/candidate_list.html:332 +#: templates/recruitment/candidate_list.html:379 +#: templates/recruitment/notification_detail.html:141 +#: templates/recruitment/training_list.html:183 +#: templates/recruitment/training_list.html:225 +#: templates/recruitment/training_update.html:184 +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:499 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:26 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:60 +msgid "Delete" +msgstr "حذف" + +#: templates/forms/form_templates_list.html:254 +#: templates/recruitment/agency_detail.html:518 +#: templates/recruitment/agency_form.html:393 +#: templates/recruitment/notification_confirm_delete.html:23 +msgid "Created:" +msgstr "تم الإنشاء:" + +#: templates/forms/form_templates_list.html:255 +msgid "ago" +msgstr "منذ" + +#: templates/forms/form_templates_list.html:273 +#: templates/recruitment/agency_confirm_delete.html:264 +#: templates/recruitment/agency_list.html:190 +#: templates/recruitment/agency_list.html:328 +#: templates/recruitment/notification_detail.html:169 +#: templates/recruitment/training_list.html:206 +msgid "Created" +msgstr "تم الإنشاء" + +#: templates/forms/form_templates_list.html:274 +#: templates/participants/participants_detail.html:218 +#, fuzzy +#| msgid "Last Updated:" +msgid "Last Updated" +msgstr "آخر تحديث" + +#: templates/forms/form_templates_list.html:346 +msgid "No Form Templates Found" +msgstr "لم يتم العثور على قوالب نماذج" + +#: templates/forms/form_templates_list.html:349 +#, python-format +msgid "No templates match your search \"%(query)s\"." +msgstr "لا توجد قوالب تطابق بحثك \"%(query)s\"." + +#: templates/forms/form_templates_list.html:351 +msgid "You haven't created any form templates yet." +msgstr "لم تقم بإنشاء أي قوالب نماذج بعد." + +#: templates/forms/form_templates_list.html:355 +msgid "Create Your First Template" +msgstr "إنشاء أول قالب لك" + +#: templates/forms/form_templates_list.html:370 +#: templates/jobs/job_detail.html:347 +#, fuzzy +#| msgid "Create New Form" +msgid "Create New Form Template" +msgstr "إنشاء قالب نموذج جديد" + +#: templates/forms/partials/candidate_facing_base.html:244 +#: templates/jobs/application_success.html:126 +msgid "KAAUH IMAGE" +msgstr "صورة مستشفى الملك عبدالله الجامعي" + +#: templates/forms/partials/candidate_facing_base.html:261 +#: templates/jobs/application_success.html:141 +msgid "Careers" +msgstr "الوظائف" + +#: templates/includes/candidate_exam_status_form.html:6 +#: templates/meetings/list_meetings.html:369 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:28 +msgid "Update" +msgstr "تحديث" + +#: templates/includes/candidate_modal_body.html:2 +#: templates/recruitment/candidate_exam_view.html:252 +#: templates/recruitment/candidate_screening_view.html:370 +#: templates/recruitment/partials/_candidate_table.html:11 +msgid "AI Score" +msgstr "نقاط الذكاء الاصطناعي" + +#: templates/includes/candidate_modal_body.html:8 +#, fuzzy +#| msgid "Job Title" +msgid "Job Fit" +msgstr "مدى ملاءمة الوظيفة" + +#: templates/includes/candidate_modal_body.html:15 +#: templates/recruitment/candidate_detail.html:511 +msgid "Top Keywords" +msgstr "أهم الكلمات المفتاحية" + +#: templates/includes/candidate_modal_body.html:29 +msgid "Experience" +msgstr "الخبرة" + +#: templates/includes/candidate_modal_body.html:31 +msgid "years" +msgstr "سنوات" + +#: templates/includes/candidate_modal_body.html:32 +msgid "Recent Role:" +msgstr "الدور الأخير:" + +#: templates/includes/candidate_modal_body.html:37 +msgid "Skills" +msgstr "المهارات" + +#: templates/includes/candidate_modal_body.html:39 +msgid "Soft Skills:" +msgstr "المهارات الشخصية:" + +#: templates/includes/candidate_modal_body.html:40 +msgid "Industry Match:" +msgstr "التطابق مع الصناعة:" + +#: templates/includes/candidate_modal_body.html:49 +#: templates/recruitment/candidate_detail.html:505 +msgid "Recommendation" +msgstr "التوصية" + +#: templates/includes/candidate_modal_body.html:54 +#: templates/recruitment/candidate_detail.html:496 +msgid "Strengths" +msgstr "نقاط القوة" + +#: templates/includes/candidate_modal_body.html:59 +#: templates/recruitment/candidate_detail.html:499 +msgid "Weaknesses" +msgstr "نقاط الضعف" + +#: templates/includes/candidate_modal_body.html:64 +#: templates/recruitment/candidate_detail.html:551 +msgid "Criteria Assessment" +msgstr "تقييم المعايير" + +#: templates/includes/candidate_modal_body.html:69 +#: templates/recruitment/candidate_detail.html:556 +#, fuzzy +#| msgid "Create Material" +msgid "Criteria" +msgstr "المعايير" + +#: templates/includes/candidate_modal_body.html:79 +#: templates/includes/candidate_modal_body.html:100 +#: templates/recruitment/candidate_detail.html:566 +msgid "Met" +msgstr "تم استيفاؤها" + +#: templates/includes/candidate_modal_body.html:81 +#: templates/includes/candidate_modal_body.html:102 +#: templates/recruitment/candidate_detail.html:568 +msgid "Not Met" +msgstr "لم يتم استيفاؤها" + +#: templates/includes/candidate_modal_body.html:97 +#, fuzzy +#| msgid "Description & Requirements" +msgid "Minimum Requirements" +msgstr "الحد الأدنى من المتطلبات" + +#: templates/includes/candidate_modal_body.html:108 +#: templates/recruitment/candidate_screening_view.html:275 +msgid "Screening Rating" +msgstr "تقييم الفرز" + +#: templates/includes/candidate_modal_body.html:116 +#: templates/recruitment/candidate_detail.html:583 +msgid "Language Fluency" +msgstr "طلاقة اللغة" + +#: templates/includes/copy_to_clipboard.html:5 +#: templates/includes/easy_logs.html:269 +#: templates/recruitment/agency_assignment_detail.html:453 +msgid "Success" +msgstr "نجاح" + +#: templates/includes/copy_to_clipboard.html:9 +#, python-format +msgid "Copied \"%(text)s\" to clipboard!" +msgstr "تم نسخ \"%(text)s\" إلى الحافظة!" + +#: templates/includes/easy_logs.html:5 +#, fuzzy +#| msgid "Dashboard" +msgid "Audit Dashboard" +msgstr "لوحة تحكم التدقيق" + +#: templates/includes/easy_logs.html:153 +msgid "System Audit Logs" +msgstr "سجلات تدقيق النظام" + +#: templates/includes/easy_logs.html:157 +msgid "Viewing Logs" +msgstr "عرض السجلات" + +#: templates/includes/easy_logs.html:159 +msgid "Displaying" +msgstr "يتم عرض" + +#: templates/includes/easy_logs.html:160 +msgid "total records." +msgstr "إجمالي السجلات." + +#: templates/includes/easy_logs.html:197 templates/includes/easy_logs.html:206 +#: templates/includes/easy_logs.html:214 +#, fuzzy +#| msgid "Start Time" +msgid "Date/Time" +msgstr "التاريخ/الوقت" + +#: templates/includes/easy_logs.html:198 templates/includes/easy_logs.html:207 +#: templates/includes/easy_logs.html:215 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:17 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:33 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:15 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:33 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:30 +msgid "User" +msgstr "المستخدم" + +#: templates/includes/easy_logs.html:200 +msgid "Model" +msgstr "النموذج" + +#: templates/includes/easy_logs.html:201 +msgid "Object PK" +msgstr "المفتاح الأساسي للكائن" + +#: templates/includes/easy_logs.html:202 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:35 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:81 +#, fuzzy +#| msgid "Change Stage" +msgid "Changes" +msgstr "التغييرات" + +#: templates/includes/easy_logs.html:208 +#: templates/recruitment/notification_detail.html:161 +#: templates/recruitment/notification_list.html:46 +#, fuzzy +#| msgid "Job Type" +msgid "Type" +msgstr "النوع" + +#: templates/includes/easy_logs.html:216 +#, fuzzy +#| msgid "HTTP Method" +msgid "Method" +msgstr "الطريقة" + +#: templates/includes/easy_logs.html:217 +msgid "Path" +msgstr "المسار" + +#: templates/includes/easy_logs.html:234 +msgid "CREATE" +msgstr "إنشاء" + +#: templates/includes/easy_logs.html:235 +msgid "UPDATE" +msgstr "تحديث" + +#: templates/includes/easy_logs.html:236 +msgid "DELETE" +msgstr "حذف" + +#: templates/includes/easy_logs.html:260 +msgid "Login" +msgstr "تسجيل الدخول" + +#: templates/includes/easy_logs.html:262 +#, fuzzy +#| msgid "Failed" +msgid "Failed Login" +msgstr "فشل تسجيل الدخول" + +#: templates/includes/easy_logs.html:288 +msgid "No logs found for this section or the database is empty." +msgstr "لم يتم العثور على سجلات لهذا القسم أو قاعدة البيانات فارغة." + +#: templates/includes/email_compose_form.html:10 +#: templates/recruitment/candidate_interview_view.html:523 +#, fuzzy +#| msgid "Host Email" +msgid "Compose Email" +msgstr "إنشاء بريد إلكتروني" + +#: templates/includes/email_compose_form.html:103 +msgid "Email will be sent to all selected recipients" +msgstr "سيتم إرسال البريد الإلكتروني إلى جميع المستلمين المحددين" + +#: templates/includes/email_compose_form.html:116 +#: templates/includes/email_compose_form.html:236 +#: templates/includes/email_compose_form.html:267 +#: templates/recruitment/agency_detail.html:501 +#, fuzzy +#| msgid "Email" +msgid "Send Email" +msgstr "إرسال بريد إلكتروني" + +#: templates/includes/email_compose_form.html:131 +#: templates/recruitment/agency_portal_submit_candidate.html:381 +#: templates/recruitment/candidate_detail.html:599 +msgid "Loading..." +msgstr "جاري التحميل..." + +#: templates/includes/email_compose_form.html:134 +msgid "Sending email..." +msgstr "جاري إرسال البريد الإلكتروني..." + +#: templates/includes/email_compose_form.html:208 +msgid "Sending..." +msgstr "جاري الإرسال..." + +#: templates/includes/meeting_form.html:15 +#, fuzzy +#| msgid "Start Time" +msgid "Start Time and Date" +msgstr "وقت وتاريخ البدء" + +#: templates/includes/meeting_form.html:20 +#: templates/meetings/create_meeting.html:170 +#: templates/meetings/reschedule_meeting.html:51 +#: templates/meetings/schedule_meeting_form.html:62 +#: templates/meetings/update_meeting.html:223 +#: templates/recruitment/schedule_meeting_form.html:72 +msgid "Duration (minutes)" +msgstr "المدة (بالدقائق)" + +#: templates/includes/meeting_form.html:25 +msgid "Meeting Details (will appear after scheduling):" +msgstr "تفاصيل الاجتماع (ستظهر بعد الجدولة):" + +#: templates/includes/meeting_form.html:26 +#, fuzzy +#| msgid "Join URL" +msgid "Join URL:" +msgstr "رابط الانضمام:" + +#: templates/includes/meeting_form.html:27 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting ID:" +msgstr "معرف الاجتماع:" + +#: templates/includes/meeting_form.html:32 +msgid "Click here to join meeting" +msgstr "انقر هنا للانضمام إلى الاجتماع" + +#: templates/includes/meeting_form.html:42 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Reschedule Meeting" +msgstr "إعادة جدولة الاجتماع" + +#: templates/includes/meeting_form.html:42 +#: templates/includes/meeting_form.html:71 +#: templates/meetings/schedule_meeting_form.html:84 +#: templates/recruitment/schedule_meeting_form.html:4 +#: templates/recruitment/schedule_meeting_form.html:86 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Schedule Meeting" +msgstr "جدولة الاجتماع" + +#: templates/includes/meeting_form.html:67 +#: templates/meetings/schedule_meeting_form.html:9 +#: templates/recruitment/candidate_interview_view.html:407 +#: templates/recruitment/schedule_meeting_form.html:15 +#, fuzzy +#| msgid "Interview" +msgid "Schedule Interview" +msgstr "جدولة مقابلة" + +#: templates/includes/meeting_form.html:83 +msgid "Processing..." +msgstr "جاري المعالجة..." + +#: templates/includes/meeting_form.html:129 +msgid "An unknown error occurred." +msgstr "حدث خطأ غير معروف." + +#: templates/includes/meeting_form.html:137 +msgid "An error occurred while processing your request." +msgstr "حدث خطأ أثناء معالجة طلبك." + +#: templates/includes/paginator.html:7 +msgid "First" +msgstr "الصفحة الأولى" + +#: templates/includes/paginator.html:10 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/pagination_infinite.html:5 +msgid "Previous" +msgstr "السابق" + +#: templates/includes/paginator.html:23 +msgid "Last" +msgstr "الصفحة الأخيرة" + +#: templates/includes/search_form.html:14 +msgid "Search..." +msgstr "بحث..." + +#: templates/interviews/schedule_interviews.html:110 +msgid "Bulk Interview Scheduling" +msgstr "الجدولة الجماعية للمقابلات" + +#: templates/interviews/schedule_interviews.html:113 +msgid "Configure time slots for:" +msgstr "تكوين الفترات الزمنية لـ:" + +#: templates/interviews/schedule_interviews.html:117 +#: templates/recruitment/candidate_exam_view.html:187 +#: templates/recruitment/candidate_hired_view.html:211 +#: templates/recruitment/candidate_interview_view.html:190 +#: templates/recruitment/candidate_offer_view.html:189 +#: templates/recruitment/candidate_screening_view.html:238 +#, fuzzy +#| msgid "Back to Jobs" +msgid "Back to Job" +msgstr "العودة إلى الوظيفة" + +#: templates/interviews/schedule_interviews.html:127 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Select Candidates" +msgstr "تحديد المرشحين" + +#: templates/interviews/schedule_interviews.html:131 +msgid "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)" +msgstr "المرشحون للجدولة (اضغط على Ctrl/Cmd لتحديد أكثر من واحد)" + +#: templates/interviews/schedule_interviews.html:141 +#, fuzzy +#| msgid "Schedule" +msgid "Schedule Details" +msgstr "تفاصيل الجدول" + +#: templates/interviews/schedule_interviews.html:183 +#, fuzzy +#| msgid "Duration (minutes)" +msgid "Duration (min)" +msgstr "المدة (دقيقة)" + +#: templates/interviews/schedule_interviews.html:190 +#, fuzzy +#| msgid "Buffer Time (minutes)" +msgid "Buffer (min)" +msgstr "الفاصل (دقيقة)" + +#: templates/interviews/schedule_interviews.html:197 +#, fuzzy +#| msgid "Break End Time" +msgid "Daily Break Times" +msgstr "أوقات الاستراحة اليومية" + +#: templates/interviews/schedule_interviews.html:218 +#, fuzzy +#| msgid "Schedule" +msgid "Preview Schedule" +msgstr "معاينة الجدول" + +#: templates/jobs/application_success.html:7 +#, fuzzy +#| msgid "Application Stage" +msgid "Application Submitted - Thank You" +msgstr "تم إرسال الطلب - شكرًا لك" + +#: templates/jobs/application_success.html:135 +#: templates/recruitment/dashboard.html:263 +msgid "Applications" +msgstr "الطلبات" + +#: templates/jobs/application_success.html:138 +msgid "Profile" +msgstr "الملف الشخصي" + +#: templates/jobs/application_success.html:150 +#, fuzzy +#| msgid "Application Information" +msgid "Application Confirmation" +msgstr "تأكيد الطلب" + +#: templates/jobs/application_success.html:168 +msgid "Thank You!" +msgstr "شكرًا لك!" + +#: templates/jobs/application_success.html:169 +msgid "Your application has been submitted successfully" +msgstr "تم إرسال طلبك بنجاح" + +#: templates/jobs/application_success.html:183 +msgid "" +"We appreciate your interest in joining our team. Our hiring team will review " +"your application and contact you if there's a potential match for this " +"position." +msgstr "" +"نقدر اهتمامك بالانضمام إلى فريقنا. سيقوم فريق التوظيف بمراجعة طلبك والاتصال " +"بك إذا كان هناك تطابق محتمل لهذا المنصب." + +#: templates/jobs/application_success.html:188 +msgid "Return to Job Listings" +msgstr "العودة إلى قوائم الوظائف" + +#: templates/jobs/career.html:224 templates/jobs/career.html:237 +msgid "Job ID#" +msgstr "معرف الوظيفة #" + +#: templates/jobs/career.html:225 templates/jobs/career.html:239 +#: templates/jobs/create_job.html:122 templates/jobs/edit_job.html:122 +#: templates/meetings/meeting_details.html:251 +#: templates/recruitment/agency_portal_assignment_detail.html:143 +msgid "Job Title" +msgstr "المسمى الوظيفي" + +#: templates/jobs/career.html:226 templates/jobs/career.html:241 +#, fuzzy +#| msgid "Hiring Agency" +msgid "Hiring" +msgstr "التوظيف" + +#: templates/jobs/career.html:227 templates/jobs/career.html:244 +#, fuzzy +#| msgid "Join Date" +msgid "Posting Date" +msgstr "تاريخ النشر" + +#: templates/jobs/career.html:228 templates/jobs/career.html:247 +#, fuzzy +#| msgid "Applied for:" +msgid "Apply Before" +msgstr "قدم قبل" + +#: templates/jobs/career.html:229 templates/jobs/career.html:249 +#: templates/recruitment/candidate_interview_view.html:266 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:27 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:98 +msgid "Link" +msgstr "الرابط" + +#: templates/jobs/career.html:253 +#: templates/participants/participants_list.html:186 +#: templates/recruitment/candidate_list.html:243 +msgid "Apply" +msgstr "تقديم" + +#: templates/jobs/create_job.html:105 templates/jobs/edit_job.html:105 +#, fuzzy +#| msgid "Create New Job Posting" +msgid "Edit Job Posting" +msgstr "تعديل إعلان الوظيفة" + +#: templates/jobs/create_job.html:105 templates/jobs/edit_job.html:105 +msgid "Create New Job Posting" +msgstr "إنشاء إعلان وظيفة جديد" + +#: templates/jobs/create_job.html:116 templates/jobs/edit_job.html:116 +msgid "Core Position Details" +msgstr "التفاصيل الأساسية للوظيفة" + +#: templates/jobs/create_job.html:129 templates/jobs/edit_job.html:129 +#: templates/meetings/meeting_details.html:254 +msgid "Job Type" +msgstr "نوع الوظيفة" + +#: templates/jobs/create_job.html:137 templates/jobs/edit_job.html:137 +msgid "Workplace Type" +msgstr "نوع مكان العمل" + +#: templates/jobs/create_job.html:144 templates/jobs/edit_job.html:144 +msgid "Application Deadline" +msgstr "الموعد النهائي للتقديم" + +#: templates/jobs/create_job.html:151 templates/jobs/edit_job.html:151 +msgid "Department" +msgstr "القسم" + +#: templates/jobs/create_job.html:158 templates/jobs/edit_job.html:158 +#: templates/recruitment/partials/stats_cards.html:37 +msgid "Open Positions" +msgstr "المناصب المفتوحة" + +#: templates/jobs/create_job.html:165 templates/jobs/edit_job.html:165 +msgid "Max Applications" +msgstr "الحد الأقصى للمتقدمين" + +#: templates/jobs/create_job.html:183 templates/jobs/edit_job.html:183 +msgid "Job Content" +msgstr "بيانات الوظيفة" + +#: templates/jobs/create_job.html:197 templates/jobs/edit_job.html:197 +msgid "Qualifications and Requirements" +msgstr "المؤهلات والمتطلبات" + +#: templates/jobs/create_job.html:211 templates/jobs/edit_job.html:211 +msgid "Benefits & Application Instructions" +msgstr "المزايا وتعليمات التقديم" + +#: templates/jobs/create_job.html:243 templates/jobs/edit_job.html:243 +msgid "Internal & Promotion" +msgstr "المعلومات الداخلية والترويج" + +#: templates/jobs/create_job.html:249 templates/jobs/edit_job.html:249 +msgid "Position Number" +msgstr "رقم المنصب" + +#: templates/jobs/create_job.html:256 templates/jobs/edit_job.html:256 +msgid "Reports To" +msgstr "يقدم تقاريره إلى" + +#: templates/jobs/create_job.html:266 templates/jobs/edit_job.html:266 +msgid "Hashtags (For Promotion/Search on Linkedin)" +msgstr "الهاشتاجات (للترويج/البحث على LinkedIn)" + +#: templates/jobs/create_job.html:269 templates/jobs/edit_job.html:269 +#, fuzzy +#| msgid "Comma-separated list of trusted IP addresses" +msgid "Comma-separated list of hashtags, e.g., #hiring, #professor" +msgstr "قائمة هاشتاجات مفصولة بفواصل، مثال: #توظيف، #أستاذ" + +#: templates/jobs/create_job.html:282 templates/jobs/edit_job.html:282 +msgid "Location & Salary" +msgstr "الموقع والراتب" + +#: templates/jobs/create_job.html:288 templates/jobs/edit_job.html:288 +#: templates/recruitment/agency_detail.html:351 +msgid "City" +msgstr "المدينة" + +#: templates/jobs/create_job.html:295 templates/jobs/edit_job.html:295 +msgid "State/Province" +msgstr "الولاية/المقاطعة" + +#: templates/jobs/create_job.html:312 templates/jobs/edit_job.html:312 +msgid "Salary Range" +msgstr "نطاق الراتب" + +#: templates/jobs/create_job.html:330 templates/jobs/edit_job.html:330 +msgid "Save Job" +msgstr "حفظ" + +#: templates/jobs/job_candidates_list.html:118 +#, fuzzy +#| msgid "Applicants" +msgid "Applicants for" +msgstr "المتقدمون لـ" + +#: templates/jobs/job_candidates_list.html:131 +#, fuzzy +#| msgid "New Application Stage" +msgid "Add New Applicant" +msgstr "إضافة متقدم جديد" + +#: templates/jobs/job_candidates_list.html:162 +#: templates/jobs/job_detail.html:315 +#, fuzzy +#| msgid "Applicants" +msgid "Total Applicants" +msgstr "إجمالي المتقدمين" + +#: templates/jobs/job_candidates_list.html:175 +#, fuzzy +#| msgid "Applicants" +msgid "Search Applicants" +msgstr "البحث عن المتقدمين" + +#: templates/jobs/job_candidates_list.html:179 +msgid "Search by name, email, phone, or stage..." +msgstr "البحث بالاسم أو البريد الإلكتروني أو الهاتف أو المرحلة..." + +#: templates/jobs/job_candidates_list.html:185 +msgid "Filter Results" +msgstr "تصفية النتائج" + +#: templates/jobs/job_candidates_list.html:190 +#: templates/recruitment/notification_list.html:200 +msgid "Clear Filters" +msgstr "مسح الفلاتر" + +#: templates/jobs/job_candidates_list.html:210 +#: templates/recruitment/candidate_list.html:230 +msgid "All Stages" +msgstr "جميع المراحل" + +#: templates/jobs/job_candidates_list.html:226 +#: templates/meetings/meeting_details.html:303 +#: templates/participants/participants_list.html:212 +#: templates/recruitment/agency_assignment_detail.html:239 +#: templates/recruitment/agency_portal_assignment_detail.html:241 +#: templates/recruitment/candidate_exam_view.html:250 +#: templates/recruitment/candidate_hired_view.html:232 +#: templates/recruitment/candidate_interview_view.html:261 +#: templates/recruitment/candidate_list.html:269 +#: templates/recruitment/candidate_offer_view.html:246 +#: templates/recruitment/candidate_screening_view.html:364 +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:19 +msgid "Name" +msgstr "الاسم" + +#: templates/jobs/job_candidates_list.html:230 +#: templates/jobs/job_candidates_list.html:343 +#: templates/recruitment/candidate_detail.html:355 +msgid "Applied Date" +msgstr "تاريخ التقديم" + +#: templates/jobs/job_candidates_list.html:253 templates/jobs/job_list.html:303 +#: templates/jobs/job_list.html:365 templates/meetings/list_meetings.html:272 +#: templates/meetings/list_meetings.html:366 +#: templates/participants/participants_list.html:232 +#: templates/participants/participants_list.html:276 +#: templates/recruitment/agency_list.html:246 +#: templates/recruitment/agency_list.html:319 +#: templates/recruitment/candidate_list.html:325 +#: templates/recruitment/candidate_list.html:373 +#: templates/recruitment/candidate_update.html:104 +#: templates/recruitment/training_list.html:177 +#: templates/recruitment/training_list.html:218 +#: templates/recruitment/training_update.html:119 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:65 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:38 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:25 +msgid "View" +msgstr "عرض" + +#: templates/jobs/job_candidates_list.html:277 +#, fuzzy +#| msgid "Rejected" +msgid "Selected" +msgstr "تم التحديد" + +#: templates/jobs/job_candidates_list.html:280 +#, fuzzy +#| msgid "Interview" +msgid "Mark Interview" +msgstr "تحديد مقابلة" + +#: templates/jobs/job_candidates_list.html:284 +#, fuzzy +#| msgid "Offer" +msgid "Mark Offer" +msgstr "تحديد عرض" + +#: templates/jobs/job_candidates_list.html:349 +#, fuzzy +#| msgid "Profile" +msgid "View Profile" +msgstr "عرض الملف الشخصي" + +#: templates/jobs/job_candidates_list.html:376 +#, fuzzy +#| msgid "No applicants yet" +msgid "No applicants found" +msgstr "لم يتم العثور على متقدمين" + +#: templates/jobs/job_candidates_list.html:377 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "There are no candidates who have applied for this position yet." +msgstr "لا يوجد مرشحون تقدموا لهذا المنصب بعد." + +#: templates/jobs/job_candidates_list.html:379 +#, fuzzy +#| msgid "Applicants" +msgid "Add First Applicant" +msgstr "إضافة أول متقدم" + +#: templates/jobs/job_detail.html:171 +msgid "JOB ID: " +msgstr "معرف الوظيفة: " + +#: templates/jobs/job_detail.html:208 +msgid "Share Public Link" +msgstr "مشاركة الرابط العام" + +#: templates/jobs/job_detail.html:212 +#: templates/meetings/meeting_details.html:270 +#: templates/meetings/meeting_details.html:475 +msgid "Copied!" +msgstr "تم النسخ!" + +#: templates/jobs/job_detail.html:220 +msgid "Administrative & Location" +msgstr "الإدارية والموقع" + +#: templates/jobs/job_detail.html:221 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit JOb" +msgstr "تعديل الوظيفة" + +#: templates/jobs/job_detail.html:228 +msgid "Position No:" +msgstr "رقم المنصب:" + +#: templates/jobs/job_detail.html:243 +msgid "Created By:" +msgstr "أنشأ بواسطة:" + +#: templates/jobs/job_detail.html:246 +#, fuzzy +#| msgid "Created at" +msgid "Created At:" +msgstr "تاريخ الإنشاء:" + +#: templates/jobs/job_detail.html:249 +#, fuzzy +#| msgid "Updated at" +msgid "Updated At:" +msgstr "تاريخ التحديث:" + +#: templates/jobs/job_detail.html:262 +msgid "Required Qualifications" +msgstr "المؤهلات المطلوبة" + +#: templates/jobs/job_detail.html:296 +msgid "Tracking" +msgstr "التتبع" + +#: templates/jobs/job_detail.html:301 +#, fuzzy +#| msgid "Form Templates" +msgid "Form Template" +msgstr "قالب النموذج" + +#: templates/jobs/job_detail.html:306 +#, fuzzy +#| msgid "Connect LinkedIn" +msgid "LinkedIn" +msgstr "LinkedIn" + +#: templates/jobs/job_detail.html:319 +#, fuzzy +#| msgid "Create Template" +msgid "Create Applicant" +msgstr "إنشاء متقدم" + +#: templates/jobs/job_detail.html:322 +#, fuzzy +#| msgid "Applicants" +msgid "Manage Applicants" +msgstr "إدارة المتقدمين" + +#: templates/jobs/job_detail.html:329 +#, fuzzy +#| msgid "Application Stage" +msgid "Applicant Stages" +msgstr "مراحل المتقدمين" + +#: templates/jobs/job_detail.html:332 +msgid "" +"The applicant tracking flow is defined by the attached Form Template. View " +"the Form Template tab to manage stages and fields." +msgstr "" +"يتم تحديد سير عمل تتبع المتقدمين بواسطة قالب النموذج المرفق. عرض علامة تبويب " +"قالب النموذج لإدارة المراحل والحقول." + +#: templates/jobs/job_detail.html:339 +msgid "Form Management" +msgstr "إدارة النماذج" + +#: templates/jobs/job_detail.html:342 +msgid "Manage the custom application forms associated with this job posting." +msgstr "إدارة نماذج الطلبات المخصصة المرتبطة بإعلان الوظيفة هذا." + +#: templates/jobs/job_detail.html:352 +#, fuzzy +#| msgid "Form Templates" +msgid "View Form Template" +msgstr "عرض قالب النموذج" + +#: templates/jobs/job_detail.html:355 +msgid "" +"This job status is not active, the form will appear once the job is made " +"active" +msgstr "حالة هذه الوظيفة غير نشطة، سيظهر النموذج بمجرد تفعيل الوظيفة" + +#: templates/jobs/job_detail.html:368 +msgid "LinkedIn Integration" +msgstr "تكامل LinkedIn" + +#: templates/jobs/job_detail.html:372 +msgid "Posted successfully!" +msgstr "تم النشر بنجاح!" + +#: templates/jobs/job_detail.html:376 +msgid "View on LinkedIn" +msgstr "عرض على LinkedIn" + +#: templates/jobs/job_detail.html:380 +msgid "Posted on:" +msgstr "تم النشر في:" + +#: templates/jobs/job_detail.html:383 +msgid "This job has not been posted to LinkedIn yet." +msgstr "لم يتم نشر هذه الوظيفة على LinkedIn بعد." + +#: templates/jobs/job_detail.html:391 +msgid "Re-post to LinkedIn" +msgstr "إعادة النشر على LinkedIn" + +#: templates/jobs/job_detail.html:391 +msgid "Post to LinkedIn" +msgstr "النشر على LinkedIn" + +#: templates/jobs/job_detail.html:396 +msgid "Upload Image for Post" +msgstr "رفع صورة للمنشور" + +#: templates/jobs/job_detail.html:401 +msgid "You need to" +msgstr "أنت بحاجة إلى" + +#: templates/jobs/job_detail.html:401 +msgid "authenticate with LinkedIn" +msgstr "المصادقة مع LinkedIn" + +#: templates/jobs/job_detail.html:401 +msgid "first." +msgstr "أولاً." + +#: templates/jobs/job_detail.html:408 +#: templates/recruitment/candidate_hired_view.html:466 +msgid "Error:" +msgstr "خطأ:" + +#: templates/jobs/job_detail.html:413 +#, fuzzy +#| msgid "LinkedIn Connected" +msgid "Update LinkedIn Content" +msgstr "تحديث محتوى LinkedIn" + +#: templates/jobs/job_detail.html:426 +msgid "Candidate Categories & Scores" +msgstr "فئات المرشحين ونقاطهم" + +#: templates/jobs/job_detail.html:441 +msgid "Key Performance Indicators" +msgstr "مؤشرات الأداء الرئيسية" + +#: templates/jobs/job_detail.html:454 +msgid "Avg. AI Score" +msgstr "متوسط نقاط الذكاء الاصطناعي" + +#: templates/jobs/job_detail.html:465 +#: templates/recruitment/partials/stats_cards.html:97 +msgid "High Potential" +msgstr "إمكانات عالية" + +#: templates/jobs/job_detail.html:476 +#, fuzzy +#| msgid "Interview" +msgid "Time to Interview" +msgstr "الوقت اللازم للمقابلة" + +#: templates/jobs/job_detail.html:487 +msgid "Avg. Exam Review" +msgstr "متوسط مراجعة الاختبار" + +#: templates/jobs/job_detail.html:497 +msgid "Vacancy Fill Rate" +msgstr "معدل شغل الشواغر" + +#: templates/jobs/job_detail.html:519 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit Job Status" +msgstr "تعديل حالة الوظيفة" + +#: templates/jobs/job_detail.html:525 +#, fuzzy +#| msgid "Sync Status" +msgid "Select New Status" +msgstr "اختيار حالة جديدة" + +#: templates/jobs/job_detail.html:531 +msgid "Status form not available. Please check your view." +msgstr "نموذج الحالة غير متاح. يرجى التحقق من طريقة العرض الخاصة بك." + +#: templates/jobs/job_detail.html:537 +#: templates/meetings/meeting_details.html:396 +#: templates/recruitment/agency_portal_assignment_detail.html:578 +#: templates/user/profile.html:147 +msgid "Save Changes" +msgstr "حفظ التغييرات" + +#: templates/jobs/job_list.html:204 +msgid "Job Postings" +msgstr "الوظائف" + +#: templates/jobs/job_list.html:207 +msgid "Create New Job" +msgstr "إنشاء وظيفة جديدة" + +#: templates/jobs/job_list.html:216 +msgid "Search by Title or Department" +msgstr "البحث بالاسم أو القسم" + +#: templates/jobs/job_list.html:226 templates/meetings/list_meetings.html:200 +#, fuzzy +#| msgid "Offer Status" +msgid "Filter by Status" +msgstr "التصفية حسب الحالة" + +#: templates/jobs/job_list.html:228 templates/meetings/list_meetings.html:202 +#: templates/recruitment/agency_assignment_list.html:86 +msgid "All Statuses" +msgstr "جميع الحالات" + +#: templates/jobs/job_list.html:229 +msgid "Draft" +msgstr "مسودة" + +#: templates/jobs/job_list.html:231 +msgid "Closed" +msgstr "مغلقة" + +#: templates/jobs/job_list.html:232 +msgid "Archived" +msgstr "أرشيف" + +#: templates/jobs/job_list.html:238 templates/meetings/list_meetings.html:215 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:7 +msgid "Apply Filters" +msgstr "تطبيق الفلاتر" + +#: templates/jobs/job_list.html:242 templates/meetings/list_meetings.html:219 +#: templates/participants/participants_list.html:190 +#: templates/recruitment/candidate_list.html:247 +#: templates/recruitment/notification_list.html:60 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:527 +msgid "Clear" +msgstr "مسح" + +#: templates/jobs/job_list.html:268 +msgid "Job Title / ID" +msgstr "المسمى الوظيفي / المعرف" + +#: templates/jobs/job_list.html:270 +msgid "Max Apps" +msgstr "الحد الأقصى للطلبات" + +#: templates/jobs/job_list.html:271 +#: templates/recruitment/agency_assignment_detail.html:144 +#: templates/recruitment/agency_assignment_list.html:115 +#: templates/recruitment/agency_portal_assignment_detail.html:158 +#: templates/recruitment/agency_portal_dashboard.html:162 +msgid "Deadline" +msgstr "الموعد النهائي" + +#: templates/jobs/job_list.html:273 +msgid "Manage Forms" +msgstr "إدارة النماذج" + +#: templates/jobs/job_list.html:276 +msgid "Applicants Metrics" +msgstr "مقاييس المتقدمين" + +#: templates/jobs/job_list.html:281 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:65 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:78 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:45 +msgid "All" +msgstr "الكل" + +#: templates/jobs/job_list.html:282 +#: templates/jobs/partials/applicant_tracking.html:112 +msgid "Screened" +msgstr "تم الفرز" + +#: templates/jobs/job_list.html:355 +#, fuzzy +#| msgid "Offer Date" +msgid "Offers Made" +msgstr "العروض المقدمة" + +#: templates/jobs/job_list.html:356 +msgid "Form" +msgstr "النموذج" + +#: templates/jobs/job_list.html:359 +msgid "N/A" +msgstr "غير متوفر" + +#: templates/jobs/job_list.html:366 +#, fuzzy +#| msgid "Job Details" +msgid "Details" +msgstr "التفاصيل" + +#: templates/jobs/job_list.html:369 +msgid "Edit Job" +msgstr "تعديل الوظيفة" + +#: templates/jobs/job_list.html:392 +#, fuzzy +#| msgid "No meetings found." +msgid "No job postings found" +msgstr "لم يتم العثور على إعلانات وظيفية" + +#: templates/jobs/job_list.html:393 +msgid "Create your first job posting to get started or adjust your filters." +msgstr "أنشئ أول إعلان وظيفي للبدء أو قم بضبط الفلاتر الخاصة بك." + +#: templates/meetings/create_meeting.html:4 +msgid "Create Zoom Meeting" +msgstr "إنشاء اجتماع Zoom" + +#: templates/meetings/create_meeting.html:151 +msgid "Create New Zoom Meeting" +msgstr "إنشاء اجتماع Zoom جديد" + +#: templates/meetings/create_meeting.html:155 +#: templates/meetings/meeting_details.html:210 +msgid "Back to Meetings" +msgstr "العودة إلى الاجتماعات" + +#: templates/meetings/delete_meeting_form.html:4 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "" +"Are you sure you want to delete this meeting? This action is irreversible." +msgstr "" +"هل أنت متأكد من رغبتك في حذف هذا الاجتماع؟ هذا الإجراء لا يمكن التراجع عنه." + +#: templates/meetings/delete_meeting_form.html:7 +#: templates/meetings/meeting_details.html:222 +msgid "Delete Meeting" +msgstr "حذف الاجتماع" + +#: templates/meetings/list_meetings.html:4 +#: templates/meetings/list_meetings.html:176 +msgid "Zoom Meetings" +msgstr "اجتماعات Zoom" + +#: templates/meetings/list_meetings.html:187 +msgid "Search by Topic" +msgstr "البحث حسب الموضوع" + +#: templates/meetings/list_meetings.html:209 +#: templates/meetings/meeting_details.html:252 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Name" +msgstr "اسم المرشح" + +#: templates/meetings/list_meetings.html:210 +#, fuzzy +#| msgid "Search templates by name..." +msgid "Search by candidate..." +msgstr "البحث حسب المرشح..." + +#: templates/meetings/list_meetings.html:264 +#: templates/meetings/list_meetings.html:309 +#: templates/user/admin_settings.html:173 +msgid "ID" +msgstr "المعرف" + +#: templates/meetings/list_meetings.html:265 +#, fuzzy +#| msgid "Started" +msgid "Start" +msgstr "بدء" + +#: templates/meetings/list_meetings.html:277 +#: templates/meetings/list_meetings.html:362 +#, fuzzy +#| msgid "Join URL" +msgid "Join" +msgstr "انضمام" + +#: templates/meetings/list_meetings.html:423 +#, fuzzy +#| msgid "No meetings found." +msgid "No Zoom meetings found" +msgstr "لم يتم العثور على اجتماعات Zoom" + +#: templates/meetings/list_meetings.html:424 +msgid "Create your first meeting or adjust your filters." +msgstr "أنشئ أول اجتماع لك أو قم بضبط الفلاتر الخاصة بك." + +#: templates/meetings/list_meetings.html:426 +msgid "Create Your First Meeting" +msgstr "إنشاء أول اجتماع لك" + +#: templates/meetings/meeting_details.html:216 +#, fuzzy +#| msgid "Update Meeting" +msgid "Edit Meeting" +msgstr "تعديل الاجتماع" + +#: templates/meetings/meeting_details.html:221 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "Are you sure you want to delete this meeting? This action is permanent." +msgstr "هل أنت متأكد من رغبتك في حذف هذا الاجتماع؟ هذا الإجراء نهائي." + +#: templates/meetings/meeting_details.html:249 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Detail" +msgstr "تفاصيل المقابلة" + +#: templates/meetings/meeting_details.html:253 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Email" +msgstr "البريد الإلكتروني للمرشح" + +#: templates/meetings/meeting_details.html:262 +#, fuzzy +#| msgid "Core Details" +msgid "Connection Details" +msgstr "تفاصيل الاتصال" + +#: templates/meetings/meeting_details.html:264 +#, fuzzy +#| msgid "Start Time" +msgid "Date & Time" +msgstr "التاريخ والوقت" + +#: templates/meetings/meeting_details.html:265 +#: templates/recruitment/notification_detail.html:71 +msgid "minutes" +msgstr "دقيقة" + +#: templates/meetings/meeting_details.html:267 +msgid "Host Email" +msgstr "بريد المضيف الإلكتروني" + +#: templates/meetings/meeting_details.html:277 +#, fuzzy +#| msgid "Join URL" +msgid "Copy URL" +msgstr "نسخ الرابط" + +#: templates/meetings/meeting_details.html:298 +msgid "Assigned Participants" +msgstr "المشاركون المعينون" + +#: templates/meetings/meeting_details.html:304 +#, fuzzy +#| msgid "Toggle navigation" +msgid "Role/Designation" +msgstr "الدور/المنصب" + +#: templates/meetings/meeting_details.html:317 +msgid "External Participants" +msgstr "المشاركون الخارجيون" + +#: templates/meetings/meeting_details.html:326 +msgid "System User" +msgstr "مستخدم النظام" + +#: templates/meetings/meeting_details.html:346 +msgid "Comments" +msgstr "التعليقات" + +#: templates/meetings/meeting_details.html:369 +#: templates/meetings/meeting_details.html:391 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit Comment" +msgstr "تعديل التعليق" + +#: templates/meetings/meeting_details.html:376 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Delete Comment" +msgstr "حذف التعليق" + +#: templates/meetings/meeting_details.html:376 +#, fuzzy +#| msgid "Are you sure you want to delete this item?" +msgid "Are you sure you want to delete this comment?" +msgstr "هل أنت متأكد من رغبتك في حذف هذا التعليق؟" + +#: templates/meetings/meeting_details.html:407 +msgid "No comments yet. Be the first to comment!" +msgstr "لا توجد تعليقات بعد. كن أول من يعلق!" + +#: templates/meetings/meeting_details.html:414 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add a New Comment" +msgstr "إضافة تعليق جديد" + +#: templates/meetings/meeting_details.html:427 +#, fuzzy +#| msgid "Submit" +msgid "Submit Comment" +msgstr "إرسال التعليق" + +#: templates/meetings/meeting_details.html:431 +msgid "You must be logged in to add a comment." +msgstr "يجب عليك تسجيل الدخول لإضافة تعليق." + +#: templates/meetings/meeting_details.html:475 +#, fuzzy +#| msgid "Failed" +msgid "Copy Failed." +msgstr "فشل النسخ." + +#: templates/meetings/reschedule_meeting.html:9 +#: templates/meetings/schedule_meeting_form.html:7 +#: templates/recruitment/schedule_meeting_form.html:13 +#, fuzzy +#| msgid "Interview" +msgid "Update Interview" +msgstr "تحديث المقابلة" + +#: templates/meetings/reschedule_meeting.html:15 +msgid "You are updating the existing meeting schedule." +msgstr "أنت تقوم بتحديث جدول الاجتماع الحالي." + +#: templates/meetings/reschedule_meeting.html:27 +#: templates/meetings/schedule_meeting_form.html:27 +#: templates/recruitment/schedule_meeting_form.html:38 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting Topic" +msgstr "موضوع الاجتماع" + +#: templates/meetings/reschedule_meeting.html:65 +#: templates/meetings/schedule_meeting_form.html:82 +#: templates/meetings/update_meeting.html:233 +msgid "Update Meeting" +msgstr "تحديث الاجتماع" + +#: templates/meetings/schedule_meeting_form.html:16 +msgid "Candidate has upcoming interviews. Updating existing schedule." +msgstr "لدى المرشح مقابلات قادمة. يتم تحديث الجدول الزمني الحالي." + +#: templates/meetings/schedule_meeting_form.html:38 +msgid "e.g., Technical Screening, HR Interview" +msgstr "على سبيل المثال: الفرز الفني، مقابلة الموارد البشرية" + +#: templates/meetings/set_candidate_form.html:5 +#: templates/recruitment/candidate_interview_view.html:509 +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/change_list.html:41 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:23 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:24 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/pagination.html:19 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:9 +msgid "Save" +msgstr "حفظ" + +#: templates/meetings/update_meeting.html:4 +#: templates/meetings/update_meeting.html:196 +msgid "Update Zoom Meeting" +msgstr "تحديث اجتماع Zoom" + +#: templates/meetings/update_meeting.html:198 +msgid "Modify the details of your scheduled meeting" +msgstr "تعديل تفاصيل اجتماعك المجدول" + +#: templates/meetings/update_meeting.html:207 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Details" +msgstr "العودة إلى التفاصيل" + +#: templates/participants/participants_create.html:94 +#, fuzzy +#| msgid "Create New Candidate" +msgid "Create New Participant" +msgstr "إنشاء مشارك جديد" + +#: templates/participants/participants_create.html:96 +#, fuzzy +#| msgid "Enter details to create a new candidate record." +msgid "Enter details to create a new participant record." +msgstr "أدخل التفاصيل لإنشاء سجل مشارك جديد." + +#: templates/participants/participants_create.html:99 +#: templates/participants/participants_create.html:101 +#: templates/participants/participants_detail.html:135 +#: templates/participants/participants_detail.html:136 +#: templates/recruitment/candidate_create.html:99 +#: templates/recruitment/candidate_create.html:101 +#: templates/recruitment/candidate_detail.html:631 +#: templates/recruitment/candidate_update.html:97 +#: templates/recruitment/candidate_update.html:99 +#: templates/recruitment/training_create.html:112 +#: templates/recruitment/training_create.html:114 +#: templates/recruitment/training_update.html:112 +#: templates/recruitment/training_update.html:114 +msgid "Back to List" +msgstr "العودة إلى القائمة" + +#: templates/participants/participants_create.html:112 +#, fuzzy +#| msgid "Basic Information" +msgid "Participant Information" +msgstr "معلومات المشارك" + +#: templates/participants/participants_create.html:131 +#, fuzzy +#| msgid "Participant Video" +msgid "Save Participant" +msgstr "حفظ المشارك" + +#: templates/participants/participants_detail.html:131 +#, fuzzy +#| msgid "Participant Video" +msgid "Participant Details" +msgstr "تفاصيل المشارك" + +#: templates/participants/participants_detail.html:139 +#, fuzzy +#| msgid "Participant Video" +msgid "Edit Participant" +msgstr "تعديل المشارك" + +#: templates/participants/participants_detail.html:140 +#, fuzzy +#| msgid "Profile" +msgid "Edit Profile" +msgstr "تعديل الملف الشخصي" + +#: templates/participants/participants_detail.html:161 +#, fuzzy +#| msgid "Candidate Information" +msgid "Contact & Role Information" +msgstr "معلومات الاتصال والدور" + +#: templates/participants/participants_detail.html:166 +#: templates/user/admin_settings.html:174 +#, fuzzy +#| msgid "First Name" +msgid "Full Name" +msgstr "الاسم الكامل" + +#: templates/participants/participants_detail.html:192 +#, fuzzy +#| msgid "Active Jobs" +msgid "Assigned Jobs" +msgstr "الوظائف المعينة" + +#: templates/participants/participants_detail.html:199 +msgid "This participant is not currently assigned to any job." +msgstr "هذا المشارك غير معين لأي وظيفة حاليًا." + +#: templates/participants/participants_detail.html:210 +msgid "Metadata" +msgstr "بيانات وصفية" + +#: templates/participants/participants_detail.html:213 +#, fuzzy +#| msgid "Created" +msgid "Record Created" +msgstr "تم إنشاء السجل" + +#: templates/participants/participants_detail.html:214 +#: templates/participants/participants_detail.html:219 +msgid "at" +msgstr "في" + +#: templates/participants/participants_detail.html:225 +msgid "Total Assigned Jobs" +msgstr "إجمالي الوظائف المعينة" + +#: templates/participants/participants_detail.html:240 +#: templates/participants/participants_list.html:322 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm Deletion" +msgstr "تأكيد الحذف" + +#: templates/participants/participants_detail.html:244 +#: templates/participants/participants_list.html:326 +#, fuzzy +#| msgid "Are you sure you want to delete this item?" +msgid "Are you sure you want to delete" +msgstr "هل أنت متأكد من رغبتك في حذف" + +#: templates/participants/participants_detail.html:248 +#: templates/participants/participants_list.html:330 +msgid "This action cannot be undone." +msgstr "لا يمكن التراجع عن هذا الإجراء." + +#: templates/participants/participants_list.html:143 +msgid "Participants List" +msgstr "قائمة المشاركين" + +#: templates/participants/participants_list.html:147 +msgid "Add New Participant" +msgstr "إضافة مشارك جديد" + +#: templates/participants/participants_list.html:156 +#: templates/recruitment/candidate_list.html:206 +msgid "Search by Name or Email" +msgstr "البحث بالاسم أو البريد الإلكتروني" + +#: templates/participants/participants_list.html:172 +msgid "Filter by Assigned Job" +msgstr "التصفية حسب الوظيفة المعينة" + +#: templates/participants/participants_list.html:174 +#: templates/recruitment/candidate_list.html:224 +#: templates/recruitment/dashboard.html:395 +msgid "All Jobs" +msgstr "جميع الوظائف" + +#: templates/participants/participants_list.html:304 +msgid "No participants found" +msgstr "لم يتم العثور على مشاركين" + +#: templates/participants/participants_list.html:305 +msgid "Create your first participant record or adjust your filters." +msgstr "أنشئ أول سجل مشارك لك أو قم بضبط الفلاتر الخاصة بك." + +#: templates/participants/participants_list.html:308 +msgid "Add Participant" +msgstr "إضافة مشارك" + +#: templates/recruitment/agency_access_link_detail.html:4 +#: templates/recruitment/agency_access_link_detail.html:12 +msgid "Access Link Details" +msgstr "تفاصيل رابط الوصول" + +#: templates/recruitment/agency_access_link_detail.html:14 +msgid "Secure access link for agency candidate submissions" +msgstr "رابط وصول آمن لتقديم المرشحين من قبل الوكالة" + +#: templates/recruitment/agency_access_link_detail.html:17 +#: templates/recruitment/agency_portal_submit_candidate.html:122 +#: templates/recruitment/agency_portal_submit_candidate.html:365 +msgid "Back to Assignment" +msgstr "العودة إلى التعيين" + +#: templates/recruitment/agency_access_link_detail.html:28 +msgid "Access Information" +msgstr "معلومات الوصول" + +#: templates/recruitment/agency_access_link_detail.html:31 +#: templates/user/admin_settings.html:194 +msgid "Inactive" +msgstr "غير نشط" + +#: templates/recruitment/agency_access_link_detail.html:66 +msgid "Max Candidates" +msgstr "الحد الأقصى للمرشحين" + +#: templates/recruitment/agency_access_link_detail.html:83 +#: templates/recruitment/agency_assignment_detail.html:172 +msgid "Access Credentials" +msgstr "وثائق الوصول" + +#: templates/recruitment/agency_access_link_detail.html:87 +#: templates/recruitment/agency_assignment_detail.html:176 +msgid "Login URL" +msgstr "رابط تسجيل الدخول" + +#: templates/recruitment/agency_access_link_detail.html:121 +#: templates/recruitment/agency_assignment_detail.html:210 +msgid "" +"Share these credentials securely with the agency. They can use this " +"information to log in and submit candidates." +msgstr "" +"شارك وثائق الاعتماد هذه بأمان مع الوكالة. يمكنهم استخدام هذه المعلومات " +"لتسجيل الدخول وتقديم المرشحين." + +#: templates/recruitment/agency_access_link_detail.html:132 +msgid "Usage Statistics" +msgstr "إحصائيات الاستخدام" + +#: templates/recruitment/agency_access_link_detail.html:137 +msgid "Total Accesses" +msgstr "إجمالي مرات الوصول" + +#: templates/recruitment/agency_access_link_detail.html:146 +msgid "Never" +msgstr "أبدًا" + +#: templates/recruitment/agency_access_link_detail.html:173 +msgid "View Assignment" +msgstr "عرض التعيين" + +#: templates/recruitment/agency_access_link_detail.html:178 +#: templates/user/admin_settings.html:232 +#, fuzzy +#| msgid "Active" +msgid "Deactivate" +msgstr "تعطيل" + +#: templates/recruitment/agency_access_link_detail.html:184 +#, fuzzy +#| msgid "Active" +msgid "Reactivate" +msgstr "إعادة التنشيط" + +#: templates/recruitment/agency_access_link_detail.html:217 +#: templates/recruitment/agency_assignment_detail.html:489 +msgid "" +"Are you sure you want to deactivate this access link? Agencies will no " +"longer be able to use it." +msgstr "" +"هل أنت متأكد من رغبتك في تعطيل رابط الوصول هذا؟ لن تتمكن الوكالات من " +"استخدامه بعد الآن." + +#: templates/recruitment/agency_access_link_detail.html:224 +#: templates/recruitment/agency_assignment_detail.html:496 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "Are you sure you want to reactivate this access link?" +msgstr "هل أنت متأكد من رغبتك في إعادة تنشيط رابط الوصول هذا؟" + +#: templates/recruitment/agency_access_link_form.html:14 +msgid "Generate a secure access link for agency to submit candidates" +msgstr "إنشاء رابط وصول آمن للوكالة لتقديم المرشحين" + +#: templates/recruitment/agency_access_link_form.html:17 +#: templates/recruitment/agency_assignment_detail.html:103 +#: templates/recruitment/agency_assignment_form.html:114 +#, fuzzy +#| msgid "Back to List" +msgid "Back to Assignments" +msgstr "العودة إلى التعيينات" + +#: templates/recruitment/agency_access_link_form.html:47 +msgid "Select the agency job assignment" +msgstr "اختر تعيين الوظيفة للوكالة" + +#: templates/recruitment/agency_access_link_form.html:62 +msgid "When will this access link expire?" +msgstr "متى سينتهي صلاحية رابط الوصول هذا؟" + +#: templates/recruitment/agency_access_link_form.html:69 +msgid "Max Submissions" +msgstr "الحد الأقصى لمرات التقديم" + +#: templates/recruitment/agency_access_link_form.html:79 +msgid "" +"Maximum number of candidates agency can submit (leave blank for unlimited)" +msgstr "" +"الحد الأقصى لعدد المرشحين الذين يمكن للوكالة تقديمهم (اتركه فارغًا لعدد غير " +"محدود)" + +#: templates/recruitment/agency_access_link_form.html:99 +msgid "Whether this access link is currently active" +msgstr "ما إذا كان رابط الوصول هذا نشطًا حاليًا" + +#: templates/recruitment/agency_access_link_form.html:105 +msgid "Notes" +msgstr "ملاحظات" + +#: templates/recruitment/agency_access_link_form.html:115 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Additional notes or instructions for the agency" +msgstr "ملاحظات أو تعليمات إضافية للوكالة" + +#: templates/recruitment/agency_access_link_form.html:122 +msgid "" +"Access links will be generated with a secure token that agencies can use to " +"log in" +msgstr "" +"سيتم إنشاء روابط الوصول باستخدام رمز آمن يمكن للوكالات استخدامه لتسجيل الدخول" + +#: templates/recruitment/agency_assignment_detail.html:98 +msgid "Assignment Details and Management" +msgstr "تفاصيل التعيين والإدارة" + +#: templates/recruitment/agency_assignment_detail.html:106 +#: templates/recruitment/agency_assignment_detail.html:357 +msgid "Edit Assignment" +msgstr "تعديل التعيين" + +#: templates/recruitment/agency_assignment_detail.html:118 +#: templates/recruitment/agency_portal_assignment_detail.html:110 +#: templates/recruitment/agency_portal_assignment_detail.html:137 +#: templates/recruitment/agency_portal_submit_candidate.html:133 +#, fuzzy +#| msgid "Edit Details" +msgid "Assignment Details" +msgstr "تفاصيل التعيين" + +#: templates/recruitment/agency_assignment_detail.html:215 +#, fuzzy +#| msgid "Meeting Details" +msgid "View Access Links Details" +msgstr "عرض تفاصيل روابط الوصول" + +#: templates/recruitment/agency_assignment_detail.html:225 +#: templates/recruitment/agency_portal_assignment_detail.html:231 +#, fuzzy +#| msgid "New Candidates" +msgid "Submitted Candidates" +msgstr "المرشحون المقدمون" + +#: templates/recruitment/agency_assignment_detail.html:229 +#, fuzzy +#| msgid "Preview" +msgid "Preview Portal" +msgstr "معاينة البوابة" + +#: templates/recruitment/agency_assignment_detail.html:240 +#: templates/recruitment/agency_portal_assignment_detail.html:242 +#: templates/recruitment/candidate_hired_view.html:233 +#: templates/recruitment/candidate_interview_view.html:262 +#: templates/recruitment/candidate_offer_view.html:247 +#, fuzzy +#| msgid "Content" +msgid "Contact" +msgstr "جهة الاتصال" + +#: templates/recruitment/agency_assignment_detail.html:242 +#: templates/recruitment/agency_portal_assignment_detail.html:244 +#, fuzzy +#| msgid "Submit" +msgid "Submitted" +msgstr "تم التقديم" + +#: templates/recruitment/agency_assignment_detail.html:280 +#: templates/recruitment/agency_portal_assignment_detail.html:319 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates submitted yet" +msgstr "لم يتم تقديم أي مرشحين بعد" + +#: templates/recruitment/agency_assignment_detail.html:282 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "" +"Candidates will appear here once the agency submits them through their " +"portal." +msgstr "سيظهر المرشحون هنا بمجرد أن تقدمهم الوكالة من خلال بوابتها." + +#: templates/recruitment/agency_assignment_detail.html:294 +#: templates/recruitment/agency_portal_assignment_detail.html:333 +#: templates/recruitment/agency_portal_dashboard.html:181 +msgid "Submission Progress" +msgstr "تقدم التقديم" + +#: templates/recruitment/agency_assignment_detail.html:325 +#: templates/recruitment/agency_portal_assignment_detail.html:175 +#: templates/recruitment/agency_portal_assignment_detail.html:364 +#, fuzzy +#| msgid "Candidates" +msgid "candidates" +msgstr "مرشحين" + +#: templates/recruitment/agency_assignment_detail.html:345 +#: templates/recruitment/agency_detail.html:492 +#: templates/recruitment/agency_portal_assignment_detail.html:513 +#, fuzzy +#| msgid "Error Message" +msgid "Send Message" +msgstr "إرسال رسالة" + +#: templates/recruitment/agency_assignment_detail.html:351 +#: templates/recruitment/agency_assignment_detail.html:433 +#, fuzzy +#| msgid "Deadline:" +msgid "Extend Deadline" +msgstr "تمديد الموعد النهائي" + +#: templates/recruitment/agency_assignment_detail.html:369 +#: templates/recruitment/agency_portal_assignment_detail.html:437 +msgid "Recent Messages" +msgstr "الرسائل الأخيرة" + +#: templates/recruitment/agency_assignment_detail.html:381 +#: templates/recruitment/agency_portal_assignment_detail.html:449 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:186 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:224 +msgid "From" +msgstr "من" + +#: templates/recruitment/agency_assignment_detail.html:385 +#: templates/recruitment/agency_portal_assignment_detail.html:453 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:22 +msgid "New" +msgstr "جديد" + +#: templates/recruitment/agency_assignment_detail.html:395 +#: templates/recruitment/agency_portal_assignment_detail.html:463 +#, fuzzy +#| msgid "View API Response" +msgid "View All Messages" +msgstr "عرض جميع الرسائل" + +#: templates/recruitment/agency_assignment_detail.html:410 +msgid "Extend Assignment Deadline" +msgstr "تمديد الموعد النهائي للتعيين" + +#: templates/recruitment/agency_assignment_detail.html:419 +#, fuzzy +#| msgid "Deadline:" +msgid "New Deadline" +msgstr "موعد نهائي جديد" + +#: templates/recruitment/agency_assignment_detail.html:424 +#, fuzzy +#| msgid "Deadline:" +msgid "Current deadline:" +msgstr "الموعد النهائي الحالي:" + +#: templates/recruitment/agency_assignment_detail.html:457 +msgid "Token copied to clipboard!" +msgstr "تم نسخ الرمز المميز إلى الحافظة!" + +#: templates/recruitment/agency_assignment_form.html:110 +msgid "Assign a job to an external hiring agency" +msgstr "تعيين وظيفة لوكالة توظيف خارجية" + +#: templates/recruitment/agency_assignment_form.html:170 +msgid "Maximum number of candidates the agency can submit" +msgstr "الحد الأقصى لعدد المرشحين الذين يمكن للوكالة تقديمهم" + +#: templates/recruitment/agency_assignment_form.html:187 +msgid "Date and time when submission period ends" +msgstr "التاريخ والوقت الذي تنتهي فيه فترة التقديم" + +#: templates/recruitment/agency_assignment_form.html:207 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Internal notes about this assignment (not visible to agency)" +msgstr "ملاحظات داخلية حول هذا التعيين (غير مرئية للوكالة)" + +#: templates/recruitment/agency_assignment_list.html:4 +#: templates/recruitment/agency_assignment_list.html:59 +msgid "Agency Assignments" +msgstr "تعيينات الوكالة" + +#: templates/recruitment/agency_assignment_list.html:62 +msgid "Total Assignments:" +msgstr "إجمالي التعيينات:" + +#: templates/recruitment/agency_assignment_list.html:67 +msgid "New Assignment" +msgstr "تعيين جديد" + +#: templates/recruitment/agency_assignment_list.html:79 +msgid "Search by agency or job title..." +msgstr "البحث حسب الوكالة أو المسمى الوظيفي..." + +#: templates/recruitment/agency_assignment_list.html:169 +#, fuzzy +#| msgid "View on LinkedIn" +msgid "View Access Link" +msgstr "عرض رابط الوصول" + +#: templates/recruitment/agency_assignment_list.html:183 +msgid "Assignments pagination" +msgstr "ترقيم صفحات التعيينات" + +#: templates/recruitment/agency_assignment_list.html:228 +#, fuzzy +#| msgid "No meetings found." +msgid "No assignments found" +msgstr "لم يتم العثور على تعيينات" + +#: templates/recruitment/agency_assignment_list.html:229 +msgid "Create your first agency assignment to get started." +msgstr "أنشئ أول تعيين وكالة لك للبدء." + +#: templates/recruitment/agency_assignment_list.html:231 +msgid "Create Assignment" +msgstr "إنشاء تعيين" + +#: templates/recruitment/agency_confirm_delete.html:4 +#: templates/recruitment/agency_confirm_delete.html:179 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Delete Agency" +msgstr "حذف الوكالة" + +#: templates/recruitment/agency_confirm_delete.html:182 +msgid "You are about to delete a hiring agency. This action cannot be undone." +msgstr "أنت على وشك حذف وكالة توظيف. لا يمكن التراجع عن هذا الإجراء." + +#: templates/recruitment/agency_confirm_delete.html:186 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Agency" +msgstr "العودة إلى الوكالة" + +#: templates/recruitment/agency_confirm_delete.html:197 +msgid "Warning: This action cannot be undone!" +msgstr "تحذير: لا يمكن التراجع عن هذا الإجراء!" + +#: templates/recruitment/agency_confirm_delete.html:199 +msgid "" +"Deleting this agency will permanently remove all associated data. Please " +"review the information below carefully before proceeding." +msgstr "" +"سيؤدي حذف هذه الوكالة إلى إزالة جميع البيانات المرتبطة بها بشكل دائم. يرجى " +"مراجعة المعلومات أدناه بعناية قبل المتابعة." + +#: templates/recruitment/agency_confirm_delete.html:208 +msgid "Agency to be Deleted" +msgstr "الوكالة المراد حذفها" + +#: templates/recruitment/agency_confirm_delete.html:277 +#, fuzzy +#| msgid "No candidates found." +msgid "Associated Candidates Found" +msgstr "تم العثور على مرشحين مرتبطين" + +#: templates/recruitment/agency_confirm_delete.html:280 +msgid "candidate(s) are associated with this agency." +msgstr "مرشح(ون) مرتبط(ون) بهذه الوكالة." + +#: templates/recruitment/agency_confirm_delete.html:283 +msgid "" +"Deleting this agency will affect these candidates. Their agency reference " +"will be removed, but the candidates themselves will not be deleted." +msgstr "" +"سيؤثر حذف هذه الوكالة على هؤلاء المرشحين. سيتم إزالة مرجع وكالتهم، لكن لن " +"يتم حذف المرشحين أنفسهم." + +#: templates/recruitment/agency_confirm_delete.html:293 +msgid "What will happen when you delete this agency?" +msgstr "ماذا سيحدث عند حذف هذه الوكالة؟" + +#: templates/recruitment/agency_confirm_delete.html:300 +msgid "The agency profile and all its information will be permanently deleted" +msgstr "سيتم حذف ملف الوكالة وجميع معلوماتها بشكل دائم" + +#: templates/recruitment/agency_confirm_delete.html:304 +#, fuzzy +#| msgid "Edit candidate information and details" +msgid "All contact information and agency details will be removed" +msgstr "ستتم إزالة جميع معلومات الاتصال وتفاصيل الوكالة" + +#: templates/recruitment/agency_confirm_delete.html:309 +msgid "Associated candidates will lose their agency reference" +msgstr "المرشحون المرتبطون سيفقدون مرجع وكالتهم" + +#: templates/recruitment/agency_confirm_delete.html:313 +msgid "Historical data linking candidates to this agency will be lost" +msgstr "ستُفقد البيانات التاريخية التي تربط المرشحين بهذه الوكالة" + +#: templates/recruitment/agency_confirm_delete.html:318 +msgid "This action cannot be undone under any circumstances" +msgstr "لا يمكن التراجع عن هذا الإجراء تحت أي ظرف من الظروف" + +#: templates/recruitment/agency_confirm_delete.html:332 +msgid "Type the agency name to confirm deletion:" +msgstr "اكتب اسم الوكالة لتأكيد الحذف:" + +#: templates/recruitment/agency_confirm_delete.html:341 +msgid "This is required to prevent accidental deletions." +msgstr "هذا مطلوب لمنع عمليات الحذف العرضية." + +#: templates/recruitment/agency_confirm_delete.html:349 +msgid "" +"I understand that this action cannot be undone and I want to permanently " +"delete this agency." +msgstr "" +"أتفهم أنه لا يمكن التراجع عن هذا الإجراء وأريد حذف هذه الوكالة بشكل دائم." + +#: templates/recruitment/agency_confirm_delete.html:364 +msgid "Delete Agency Permanently" +msgstr "حذف الوكالة بشكل دائم" + +#: templates/recruitment/agency_confirm_delete.html:402 +#, fuzzy +#| msgid "Are you sure you want to delete this candidate?" +msgid "" +"Are you absolutely sure you want to delete this agency? This action cannot " +"be undone." +msgstr "" +"هل أنت متأكد تمامًا من رغبتك في حذف هذه الوكالة؟ لا يمكن التراجع عن هذا " +"الإجراء." + +#: templates/recruitment/agency_detail.html:4 +#, fuzzy +#| msgid "Core Details" +msgid "Agency Details" +msgstr "تفاصيل الوكالة" + +#: templates/recruitment/agency_detail.html:220 +msgid "Hiring Agency Details and Candidate Management" +msgstr "تفاصيل وكالة التوظيف وإدارة المرشحين" + +#: templates/recruitment/agency_detail.html:225 +msgid "Assign job" +msgstr "تعيين وظيفة" + +#: templates/recruitment/agency_detail.html:228 +#: templates/recruitment/agency_detail.html:486 +#, fuzzy +#| msgid "Hiring Agency" +msgid "Edit Agency" +msgstr "تعديل الوكالة" + +#: templates/recruitment/agency_detail.html:231 +#: templates/recruitment/agency_form.html:131 +#, fuzzy +#| msgid "Back to List" +msgid "Back to Agencies" +msgstr "العودة إلى الوكالات" + +#: templates/recruitment/agency_detail.html:248 +#: templates/recruitment/agency_list.html:285 +#, fuzzy +#| msgid "Contact & Job" +msgid "Contact:" +msgstr "جهة الاتصال:" + +#: templates/recruitment/agency_detail.html:280 +#: templates/recruitment/agency_form.html:216 +#: templates/recruitment/agency_portal_submit_candidate.html:214 +msgid "Contact Information" +msgstr "معلومات الاتصال" + +#: templates/recruitment/agency_detail.html:330 +#: templates/recruitment/agency_form.html:273 +msgid "Location Information" +msgstr "معلومات الموقع" + +#: templates/recruitment/agency_detail.html:391 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Recent Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/agency_detail.html:394 +#: templates/recruitment/agency_detail.html:489 +#, fuzzy +#| msgid "All Candidates" +msgid "View All Candidates" +msgstr "عرض جميع المرشحين" + +#: templates/recruitment/agency_detail.html:427 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates yet" +msgstr "لا يوجد مرشحون بعد" + +#: templates/recruitment/agency_detail.html:428 +msgid "This agency hasn't submitted any candidates yet." +msgstr "لم تقدم هذه الوكالة أي مرشحين بعد." + +#: templates/recruitment/agency_detail.html:442 +#, fuzzy +#| msgid "Candidates" +msgid "Candidate Statistics" +msgstr "إحصائيات المرشحين" + +#: templates/recruitment/agency_detail.html:450 +msgid "Total" +msgstr "الإجمالي" + +#: templates/recruitment/agency_detail.html:480 +#, fuzzy +#| msgid "Actions" +msgid "Quick Actions" +msgstr "الإجراءات السريعة" + +#: templates/recruitment/agency_detail.html:496 +msgid "Visit Website" +msgstr "زيارة الموقع الإلكتروني" + +#: templates/recruitment/agency_detail.html:513 +#: templates/recruitment/agency_form.html:390 +#, fuzzy +#| msgid "Join Information" +msgid "Agency Information" +msgstr "معلومات الوكالة" + +#: templates/recruitment/agency_detail.html:522 +#: templates/recruitment/agency_form.html:397 +msgid "Last Updated:" +msgstr "آخر تحديث:" + +#: templates/recruitment/agency_detail.html:526 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency ID:" +msgstr "معرف الوكالة:" + +#: templates/recruitment/agency_form.html:124 +msgid "Update the hiring agency information below." +msgstr "قم بتحديث معلومات وكالة التوظيف أدناه." + +#: templates/recruitment/agency_form.html:126 +msgid "Fill in the details to add a new hiring agency." +msgstr "املأ التفاصيل لإضافة وكالة توظيف جديدة." + +#: templates/recruitment/agency_form.html:144 +msgid "Please correct the errors below:" +msgstr "يرجى تصحيح الأخطاء أدناه:" + +#: templates/recruitment/agency_form.html:159 +msgid "Basic Information" +msgstr "المعلومات الأساسية" + +#: templates/recruitment/agency_form.html:313 +msgid "Additional Information" +msgstr "معلومات إضافية" + +#: templates/recruitment/agency_form.html:362 +msgid "Quick Tips" +msgstr "نصائح سريعة" + +#: templates/recruitment/agency_form.html:367 +msgid "Provide accurate contact information for better communication" +msgstr "قدم معلومات اتصال دقيقة لتواصل أفضل" + +#: templates/recruitment/agency_form.html:371 +msgid "Include a valid website URL if available" +msgstr "أدرج رابط موقع إلكتروني صالح إذا كان متاحًا" + +#: templates/recruitment/agency_form.html:375 +msgid "Add a detailed description to help identify the agency" +msgstr "أضف وصفًا مفصلاً للمساعدة في تحديد الوكالة" + +#: templates/recruitment/agency_form.html:379 +msgid "All fields marked with * are required" +msgstr "جميع الحقول المميزة بعلامة * مطلوبة" + +#: templates/recruitment/agency_form.html:401 +#, fuzzy +#| msgid "Slug" +msgid "Slug:" +msgstr "الرابط المختصر:" + +#: templates/recruitment/agency_list.html:134 +#, fuzzy +#| msgid "Hiring Agencies" +msgid "Total Agencies:" +msgstr "إجمالي الوكالات:" + +#: templates/recruitment/agency_list.html:141 +msgid "View All Job Assignments" +msgstr "عرض جميع تعيينات الوظائف" + +#: templates/recruitment/agency_list.html:145 +msgid "Add New Agency" +msgstr "إضافة وكالة جديدة" + +#: templates/recruitment/agency_list.html:161 +msgid "Search by name, contact person, email, or country..." +msgstr "البحث بالاسم أو جهة الاتصال أو البريد الإلكتروني أو البلد..." + +#: templates/recruitment/agency_list.html:341 +msgid "Agency pagination" +msgstr "ترقيم صفحات الوكالة" + +#: templates/recruitment/agency_list.html:385 +#, fuzzy +#| msgid "No templates match your search \"%(query)s\"." +msgid "No agencies found matching your search criteria." +msgstr "لم يتم العثور على وكالات تطابق معايير البحث الخاصة بك." + +#: templates/recruitment/agency_list.html:387 +msgid "No hiring agencies have been added yet." +msgstr "لم تتم إضافة وكالات توظيف بعد." + +#: templates/recruitment/agency_list.html:391 +msgid "" +"Start by adding your first hiring agency to manage your recruitment partners." +msgstr "ابدأ بإضافة أول وكالة توظيف لك لإدارة شركاء التوظيف لديك." + +#: templates/recruitment/agency_list.html:394 +#, fuzzy +#| msgid "Add Your First Candidate" +msgid "Add Your First Agency" +msgstr "أضف وكالتك الأولى" + +#: templates/recruitment/agency_portal_assignment_detail.html:115 +#, fuzzy +#| msgid "Dashboard" +msgid "Back to Dashboard" +msgstr "العودة إلى لوحة التحكم" + +#: templates/recruitment/agency_portal_assignment_detail.html:118 +#: templates/recruitment/agency_portal_submit_candidate.html:114 +#, fuzzy +#| msgid "New Candidates" +msgid "Submit New Candidate" +msgstr "تقديم مرشح جديد" + +#: templates/recruitment/agency_portal_assignment_detail.html:169 +#, fuzzy +#| msgid "Training" +msgid "days remaining" +msgstr "أيام متبقية" + +#: templates/recruitment/agency_portal_assignment_detail.html:182 +#, fuzzy +#| msgid "Job Description" +msgid "Job Description " +msgstr "وصف الوظيفة" + +#: templates/recruitment/agency_portal_assignment_detail.html:269 +#: templates/recruitment/agency_portal_assignment_detail.html:528 +#, fuzzy +#| msgid "Update Candidate" +msgid "Edit Candidate" +msgstr "تعديل المرشح" + +#: templates/recruitment/agency_portal_assignment_detail.html:272 +#: templates/recruitment/agency_portal_assignment_detail.html:593 +#: templates/recruitment/agency_portal_assignment_detail.html:612 +#, fuzzy +#| msgid "Create Candidate" +msgid "Remove Candidate" +msgstr "إزالة المرشح" + +#: templates/recruitment/agency_portal_assignment_detail.html:321 +msgid "Submit candidates using the form above to get started." +msgstr "قدم المرشحين باستخدام النموذج أعلاه للبدء." + +#: templates/recruitment/agency_portal_assignment_detail.html:374 +#: templates/recruitment/agency_portal_submit_candidate.html:156 +#, fuzzy +#| msgid "Submit" +msgid "Can Submit" +msgstr "يمكن التقديم" + +#: templates/recruitment/agency_portal_assignment_detail.html:376 +#: templates/recruitment/agency_portal_submit_candidate.html:158 +#, fuzzy +#| msgid "Submit" +msgid "Cannot Submit" +msgstr "لا يمكن التقديم" + +#: templates/recruitment/agency_portal_assignment_detail.html:406 +msgid "Assignment Info" +msgstr "معلومات التعيين" + +#: templates/recruitment/agency_portal_assignment_detail.html:415 +msgid "Days Remaining" +msgstr "الأيام المتبقية" + +#: templates/recruitment/agency_portal_assignment_detail.html:417 +#: templates/recruitment/agency_portal_submit_candidate.html:147 +msgid "days" +msgstr "يوم" + +#: templates/recruitment/agency_portal_assignment_detail.html:422 +msgid "Submission Rate" +msgstr "معدل التقديم" + +#: templates/recruitment/agency_portal_assignment_detail.html:478 +msgid "Send Message to Admin" +msgstr "إرسال رسالة إلى المسؤول" + +#: templates/recruitment/agency_portal_assignment_detail.html:493 +msgid "Priority" +msgstr "الأولوية" + +#: templates/recruitment/agency_portal_assignment_detail.html:495 +msgid "Low" +msgstr "منخفض" + +#: templates/recruitment/agency_portal_assignment_detail.html:496 +msgid "Medium" +msgstr "متوسط" + +#: templates/recruitment/agency_portal_assignment_detail.html:497 +msgid "High" +msgstr "مرتفع" + +#: templates/recruitment/agency_portal_assignment_detail.html:498 +#, fuzzy +#| msgid "User Agent" +msgid "Urgent" +msgstr "عاجل" + +#: templates/recruitment/agency_portal_assignment_detail.html:603 +#, fuzzy +#| msgid "Are you sure you want to delete this candidate?" +msgid "" +"Are you sure you want to remove this candidate? This action cannot be undone." +msgstr "" +"هل أنت متأكد من رغبتك في إزالة هذا المرشح؟ لا يمكن التراجع عن هذا الإجراء." + +#: templates/recruitment/agency_portal_assignment_detail.html:605 +#, fuzzy +#| msgid "Candidate" +msgid "Candidate:" +msgstr "المرشح:" + +#: templates/recruitment/agency_portal_assignment_detail.html:644 +msgid "Error loading candidate data. Please try again." +msgstr "خطأ في تحميل بيانات المرشح. يرجى المحاولة مرة أخرى." + +#: templates/recruitment/agency_portal_assignment_detail.html:679 +#: templates/recruitment/agency_portal_assignment_detail.html:684 +msgid "Error updating candidate. Please try again." +msgstr "خطأ في تحديث المرشح. يرجى المحاولة مرة أخرى." + +#: templates/recruitment/agency_portal_assignment_detail.html:706 +#: templates/recruitment/agency_portal_assignment_detail.html:711 +msgid "Error removing candidate. Please try again." +msgstr "خطأ في إزالة المرشح. يرجى المحاولة مرة أخرى." + +#: templates/recruitment/agency_portal_dashboard.html:4 +#: templates/recruitment/agency_portal_dashboard.html:45 +#, fuzzy +#| msgid "Dashboard" +msgid "Agency Dashboard" +msgstr "لوحة تحكم الوكالة" + +#: templates/recruitment/agency_portal_dashboard.html:48 +msgid "Welcome back" +msgstr "مرحبًا بعودتك" + +#: templates/recruitment/agency_portal_dashboard.html:76 +msgid "Total Assignments" +msgstr "إجمالي المهام" + +#: templates/recruitment/agency_portal_dashboard.html:87 +msgid "Active Assignments" +msgstr "المهام النشطة" + +#: templates/recruitment/agency_portal_dashboard.html:98 +#: templates/recruitment/partials/stats_cards.html:28 +#, fuzzy +#| msgid "All Candidates" +msgid "Total Candidates" +msgstr "إجمالي المرشحين" + +#: templates/recruitment/agency_portal_dashboard.html:109 +#, fuzzy +#| msgid "Error Message" +msgid "Unread Messages" +msgstr "الرسائل غير المقروءة" + +#: templates/recruitment/agency_portal_dashboard.html:121 +msgid "Your Job Assignments" +msgstr "مهام الوظائف الخاصة بك" + +#: templates/recruitment/agency_portal_dashboard.html:123 +msgid "assignments" +msgstr "تعيينات" + +#: templates/recruitment/agency_portal_dashboard.html:166 +msgid "days left" +msgstr "أيام متبقية" + +#: templates/recruitment/agency_portal_dashboard.html:168 +msgid "days overdue" +msgstr "أيام متأخرة" + +#: templates/recruitment/agency_portal_dashboard.html:202 +msgid "Submissions Closed" +msgstr "التقديمات مغلقة" + +#: templates/recruitment/agency_portal_dashboard.html:230 +msgid "No Job Assignments Found" +msgstr "لم يتم العثور على مهام وظائف" + +#: templates/recruitment/agency_portal_dashboard.html:232 +msgid "" +"You don't have any job assignments yet. Please contact the administrator if " +"you expect to have assignments." +msgstr "" +"ليس لديك أي مهام وظائف حتى الآن. يرجى التواصل مع المسؤول إذا كنت تتوقع وجود " +"مهام." + +#: templates/recruitment/agency_portal_login.html:4 +msgid "Agency Portal Login" +msgstr "تسجيل الدخول إلى بوابة الوكالة" + +#: templates/recruitment/agency_portal_login.html:128 +#, fuzzy +#| msgid "Edit candidate information and details" +msgid "Submit candidates for job assignments" +msgstr "تقديم المرشحين للتعيينات الوظيفية" + +#: templates/recruitment/agency_portal_login.html:159 +msgid "Enter the access token provided by the hiring organization" +msgstr "أدخل رمز الوصول المميز المقدم من قبل منظمة التوظيف" + +#: templates/recruitment/agency_portal_login.html:181 +msgid "Enter the password for this access token" +msgstr "أدخل كلمة المرور لرمز الوصول المميز هذا" + +#: templates/recruitment/agency_portal_login.html:189 +msgid "Access Portal" +msgstr "الوصول إلى البوابة" + +#: templates/recruitment/agency_portal_login.html:198 +msgid "Need Help?" +msgstr "هل تحتاج إلى مساعدة؟" + +#: templates/recruitment/agency_portal_login.html:206 +#, fuzzy +#| msgid "Contact & Job" +msgid "Contact Support" +msgstr "الاتصال بالدعم" + +#: templates/recruitment/agency_portal_login.html:208 +msgid "Reach out to your hiring contact" +msgstr "تواصل مع جهة الاتصال الخاصة بالتوظيف" + +#: templates/recruitment/agency_portal_login.html:215 +#, fuzzy +#| msgid "Duration" +msgid "Documentation" +msgstr "التوثيق" + +#: templates/recruitment/agency_portal_login.html:217 +msgid "View user guides and tutorials" +msgstr "عرض أدلة المستخدم والبرامج التعليمية" + +#: templates/recruitment/agency_portal_login.html:227 +msgid "Security Notice" +msgstr "إشعار أمني" + +#: templates/recruitment/agency_portal_login.html:230 +msgid "" +"This portal is for authorized agency partners only. Access is monitored and " +"logged." +msgstr "" +"هذه البوابة مخصصة لشركاء الوكالة المعتمدين فقط. يتم مراقبة وتسجيل الوصول." + +#: templates/recruitment/agency_portal_login.html:234 +msgid "" +"If you believe you've received this link in error, please contact the hiring " +"organization immediately." +msgstr "" +"إذا كنت تعتقد أنك تلقيت هذا الرابط عن طريق الخطأ، فيرجى الاتصال بمنظمة " +"التوظيف على الفور." + +#: templates/recruitment/agency_portal_login.html:295 +msgid "Please enter your access token." +msgstr "الرجاء إدخال رمز الوصول المميز الخاص بك." + +#: templates/recruitment/agency_portal_login.html:302 +msgid "Please enter your password." +msgstr "الرجاء إدخال كلمة المرور الخاصة بك." + +#: templates/recruitment/agency_portal_submit_candidate.html:117 +#, fuzzy +#| msgid "No candidates found." +msgid "Submit a candidate for" +msgstr "قدم مرشحًا لـ" + +#: templates/recruitment/agency_portal_submit_candidate.html:136 +#, fuzzy +#| msgid "Position No:" +msgid "Position:" +msgstr "المنصب:" + +#: templates/recruitment/agency_portal_submit_candidate.html:145 +msgid "Days Remaining:" +msgstr "الأيام المتبقية:" + +#: templates/recruitment/agency_portal_submit_candidate.html:154 +#, fuzzy +#| msgid "Status" +msgid "Status:" +msgstr "الحالة:" + +#: templates/recruitment/agency_portal_submit_candidate.html:170 +#: templates/recruitment/candidate_create.html:112 +msgid "Candidate Information" +msgstr "معلومات المرشح" + +#: templates/recruitment/agency_portal_submit_candidate.html:226 +#, fuzzy +#| msgid "Enter email" +msgid "Enter email address" +msgstr "أدخل عنوان البريد الإلكتروني" + +#: templates/recruitment/agency_portal_submit_candidate.html:246 +#, fuzzy +#| msgid "Candidate Information" +msgid "Address Information" +msgstr "معلومات العنوان" + +#: templates/recruitment/agency_portal_submit_candidate.html:251 +#, fuzzy +#| msgid "IP Address" +msgid "Full Address" +msgstr "العنوان الكامل" + +#: templates/recruitment/agency_portal_submit_candidate.html:258 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter full address" +msgstr "أدخل العنوان الكامل" + +#: templates/recruitment/agency_portal_submit_candidate.html:267 +#, fuzzy +#| msgid "Resume" +msgid "Resume/CV" +msgstr "السيرة الذاتية / CV" + +#: templates/recruitment/agency_portal_submit_candidate.html:272 +#, fuzzy +#| msgid "Download Resume" +msgid "Upload Resume" +msgstr "تحميل السيرة الذاتية" + +#: templates/recruitment/agency_portal_submit_candidate.html:284 +msgid "Click to upload or drag and drop" +msgstr "انقر للتحميل أو اسحب وأفلت" + +#: templates/recruitment/agency_portal_submit_candidate.html:286 +msgid "Accepted formats: PDF, DOC, DOCX (Maximum 5MB)" +msgstr "التنسيقات المقبولة: PDF، DOC، DOCX (الحد الأقصى 5 ميجابايت)" + +#: templates/recruitment/agency_portal_submit_candidate.html:294 +msgid "Remove File" +msgstr "إزالة الملف" + +#: templates/recruitment/agency_portal_submit_candidate.html:306 +msgid "Additional Notes" +msgstr "ملاحظات إضافية" + +#: templates/recruitment/agency_portal_submit_candidate.html:311 +msgid "Notes (Optional)" +msgstr "ملاحظات (اختياري)" + +#: templates/recruitment/agency_portal_submit_candidate.html:317 +msgid "Any additional information about the candidate" +msgstr "أي معلومات إضافية حول المرشح" + +#: templates/recruitment/agency_portal_submit_candidate.html:328 +msgid "Submitted candidates will be reviewed by the hiring team." +msgstr "سيتم مراجعة المرشحين المقدمين من قبل فريق التوظيف." + +#: templates/recruitment/agency_portal_submit_candidate.html:349 +#, fuzzy +#| msgid "Create Candidate" +msgid "Cannot Submit Candidates" +msgstr "لا يمكن تقديم المرشحين" + +#: templates/recruitment/agency_portal_submit_candidate.html:353 +msgid "This assignment has expired. Submissions are no longer accepted." +msgstr "انتهت صلاحية هذا التعيين. لم يعد يتم قبول التقديمات." + +#: templates/recruitment/agency_portal_submit_candidate.html:356 +msgid "Maximum candidate limit reached for this assignment." +msgstr "تم الوصول إلى الحد الأقصى لعدد المرشحين لهذا التعيين." + +#: templates/recruitment/agency_portal_submit_candidate.html:359 +msgid "This assignment is not currently active." +msgstr "هذا التعيين غير نشط حاليًا." + +#: templates/recruitment/agency_portal_submit_candidate.html:383 +msgid "Submitting candidate..." +msgstr "جاري تقديم المرشح..." + +#: templates/recruitment/agency_portal_submit_candidate.html:384 +msgid "Please wait while we process your submission." +msgstr "يرجى الانتظار بينما نقوم بمعالجة طلبك." + +#: templates/recruitment/agency_portal_submit_candidate.html:450 +msgid "Please upload a PDF, DOC, or DOCX file." +msgstr "الرجاء تحميل ملف PDF، DOC، أو DOCX." + +#: templates/recruitment/agency_portal_submit_candidate.html:457 +msgid "File size must be less than 5MB." +msgstr "يجب أن يكون حجم الملف أقل من 5 ميجابايت." + +#: templates/recruitment/agency_portal_submit_candidate.html:478 +#, fuzzy +#| msgid "Submit" +msgid "Submitting..." +msgstr "جاري الإرسال..." + +#: templates/recruitment/agency_portal_submit_candidate.html:500 +#, fuzzy +#| msgid "Posted successfully!" +msgid "Candidate submitted successfully!" +msgstr "تم تقديم المرشح بنجاح!" + +#: templates/recruitment/agency_portal_submit_candidate.html:531 +msgid "Error submitting candidate. Please try again." +msgstr "خطأ في تقديم المرشح. يرجى المحاولة مرة أخرى." + +#: templates/recruitment/agency_portal_submit_candidate.html:553 +msgid "Network error. Please check your connection and try again." +msgstr "خطأ في الشبكة. يرجى التحقق من اتصالك والمحاولة مرة أخرى." + +#: templates/recruitment/candidate_create.html:94 +msgid "Create New Candidate" +msgstr "إنشاء مرشح جديد" + +#: templates/recruitment/candidate_create.html:96 +msgid "Enter details to create a new candidate record." +msgstr "أدخل التفاصيل لإنشاء سجل مرشح جديد." + +#: templates/recruitment/candidate_create.html:131 +msgid "Create Candidate" +msgstr "إنشاء مرشح" + +#: templates/recruitment/candidate_detail.html:292 +msgid "Stage:" +msgstr "المرحلة:" + +#: templates/recruitment/candidate_detail.html:297 +msgid "Applied for:" +msgstr "تقدم لـ:" + +#: templates/recruitment/candidate_detail.html:303 +#: templates/recruitment/candidate_exam_view.html:229 +#: templates/recruitment/candidate_interview_view.html:222 +#: templates/recruitment/candidate_offer_view.html:222 +#: templates/recruitment/candidate_screening_view.html:341 +msgid "Change Stage" +msgstr "تغيير المرحلة" + +#: templates/recruitment/candidate_detail.html:313 +msgid "Contact & Job" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/candidate_detail.html:320 +msgid "Journey Timeline" +msgstr "الجدول الزمني للرحلة" + +#: templates/recruitment/candidate_detail.html:331 +msgid "Core Details" +msgstr "التفاصيل الأساسية" + +#: templates/recruitment/candidate_detail.html:346 +msgid "Position Applied" +msgstr "المنصب الذي تم التقديم عليه" + +#: templates/recruitment/candidate_detail.html:377 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Journey" +msgstr "رحلة المرشح" + +#: templates/recruitment/candidate_detail.html:381 +#, fuzzy +#| msgid "Change Stage" +msgid "Current Stage" +msgstr "المرحلة الحالية" + +#: templates/recruitment/candidate_detail.html:385 +#, fuzzy +#| msgid "Last Updated:" +msgid "Latest status update:" +msgstr "آخر تحديث للحالة:" + +#: templates/recruitment/candidate_detail.html:389 +#, fuzzy +#| msgid "Financial & Timeline" +msgid "Historical Timeline" +msgstr "الجدول الزمني التاريخي" + +#: templates/recruitment/candidate_detail.html:397 +#, fuzzy +#| msgid "Application Stage" +msgid "Application Submitted" +msgstr "تم تقديم الطلب" + +#: templates/recruitment/candidate_detail.html:458 +msgid "AI Generated Summary" +msgstr "ملخص تم إنشاؤه بواسطة الذكاء الاصطناعي" + +#: templates/recruitment/candidate_detail.html:468 +msgid "AI Analysis Report" +msgstr "تقرير التحليل بالذكاء الاصطناعي" + +#: templates/recruitment/candidate_detail.html:474 +msgid "Match Score" +msgstr "نقاط التوافق" + +#: templates/recruitment/candidate_detail.html:487 +msgid "Category" +msgstr "الفئة" + +#: templates/recruitment/candidate_detail.html:490 +msgid "Job Fit Narrative" +msgstr "وصف التوافق مع الوظيفة" + +#: templates/recruitment/candidate_detail.html:521 +#, fuzzy +#| msgid "Material Details" +msgid "Professional Details" +msgstr "التفاصيل المهنية" + +#: templates/recruitment/candidate_detail.html:522 +msgid "Years of Experience:" +msgstr "سنوات الخبرة:" + +#: templates/recruitment/candidate_detail.html:523 +msgid "Most Recent Job Title:" +msgstr "أحدث مسمى وظيفي:" + +#: templates/recruitment/candidate_detail.html:524 +msgid "Experience Industry Match:" +msgstr "توافق الصناعة مع الخبرة:" + +#: templates/recruitment/candidate_detail.html:529 +msgid "Soft Skills Score:" +msgstr "نقاط المهارات الشخصية:" + +#: templates/recruitment/candidate_detail.html:534 +#, fuzzy +#| msgid "Sync Status" +msgid "Screening Status" +msgstr "حالة الفرز" + +#: templates/recruitment/candidate_detail.html:536 +msgid "Minimum Requirements Met:" +msgstr "تم استيفاء الحد الأدنى من المتطلبات:" + +#: templates/recruitment/candidate_detail.html:544 +msgid "Screening Stage Rating:" +msgstr "تقييم مرحلة الفرز:" + +#: templates/recruitment/candidate_detail.html:601 +msgid "Resume is being parsed" +msgstr "جاري تحليل السيرة الذاتية" + +#: templates/recruitment/candidate_detail.html:602 +msgid "" +"Our AI is analyzing the candidate's resume to generate insights. This may " +"take a few moments." +msgstr "" +"يقوم الذكاء الاصطناعي الخاص بنا بتحليل السيرة الذاتية للمرشح لتوليد رؤى. قد " +"يستغرق هذا بضع لحظات." + +#: templates/recruitment/candidate_detail.html:622 +msgid "Management Actions" +msgstr "إجراءات الإدارة" + +#: templates/recruitment/candidate_detail.html:625 +msgid "Edit Details" +msgstr "تعديل التفاصيل" + +#: templates/recruitment/candidate_detail.html:627 +msgid "Are you sure you want to delete this candidate?" +msgstr "هل أنت متأكد من رغبتك في حذف هذا المرشح؟" + +#: templates/recruitment/candidate_detail.html:628 +msgid "Delete Candidate" +msgstr "حذف المرشح" + +#: templates/recruitment/candidate_detail.html:637 +#, fuzzy +#| msgid "View API Response" +msgid "View Actual Resume" +msgstr "عرض السيرة الذاتية الفعلية" + +#: templates/recruitment/candidate_detail.html:641 +msgid "Download Resume" +msgstr "تحميل السيرة الذاتية" + +#: templates/recruitment/candidate_detail.html:646 +msgid "View Resume AI Overview" +msgstr "عرض نظرة عامة الذكاء الاصطناعي للسيرة الذاتية" + +#: templates/recruitment/candidate_detail.html:654 +#, fuzzy +#| msgid "Time to Hire:  " +msgid "Time to Hire: " +msgstr "الوقت اللازم للتوظيف: " + +#: templates/recruitment/candidate_detail.html:679 +msgid "Unable to Parse Resume , click to retry" +msgstr "تعذر تحليل السيرة الذاتية، انقر للمحاولة مرة أخرى" + +#: templates/recruitment/candidate_exam_view.html:174 +msgid "Exam Management" +msgstr "إدارة الاختبارات" + +#: templates/recruitment/candidate_exam_view.html:177 +msgid "Candidates in Exam Stage:" +msgstr "المرشحون في مرحلة الاختبار:" + +#: templates/recruitment/candidate_exam_view.html:183 +msgid "Export exam candidates to CSV" +msgstr "تصدير مرشحي الاختبار إلى CSV" + +#: templates/recruitment/candidate_exam_view.html:184 +#: templates/recruitment/candidate_hired_view.html:208 +#: templates/recruitment/candidate_interview_view.html:187 +#: templates/recruitment/candidate_offer_view.html:186 +#: templates/recruitment/candidate_screening_view.html:235 +msgid "Export CSV" +msgstr "تصدير CSV" + +#: templates/recruitment/candidate_exam_view.html:197 +#: templates/recruitment/candidate_screening_view.html:312 +msgid "Candidate List" +msgstr "قائمة المرشحين" + +#: templates/recruitment/candidate_exam_view.html:199 +#, fuzzy +#| msgid "AI Score" +msgid "Sorted by AI Score" +msgstr "نقاط الذكاء الاصطناعي" + +#: templates/recruitment/candidate_exam_view.html:219 +msgid "Interview Stage" +msgstr "مرحلة المقابلة" + +#: templates/recruitment/candidate_exam_view.html:222 +msgid "Screening Stage" +msgstr "مرحلة الفرز" + +#: templates/recruitment/candidate_exam_view.html:251 +#: templates/recruitment/candidate_screening_view.html:367 +msgid "Contact Info" +msgstr "معلومات الاتصال" + +#: templates/recruitment/candidate_exam_view.html:254 +msgid "Exam Results" +msgstr "نتائج الاختبار" + +#: templates/recruitment/candidate_exam_view.html:330 +msgid "No candidates are currently in the Exam stage for this job." +msgstr "لا يوجد مرشحون حاليًا في مرحلة الاختبار لهذه الوظيفة." + +#: templates/recruitment/candidate_exam_view.html:343 +msgid "Candidate Details & Exam Update" +msgstr "تفاصيل المرشح وتحديث الاختبار" + +#: templates/recruitment/candidate_exam_view.html:350 +#: templates/recruitment/candidate_screening_view.html:490 +#, fuzzy +#| msgid "No candidates found." +msgid "Loading candidate data..." +msgstr "جاري تحميل بيانات المرشح..." + +#: templates/recruitment/candidate_hired_view.html:192 +#, fuzzy +#| msgid "New Candidates" +msgid "Hired Candidates" +msgstr "المرشحون الذين تم تعيينهم" + +#: templates/recruitment/candidate_hired_view.html:195 +msgid "Successfully Hired:" +msgstr "تم التوظيف بنجاح:" + +#: templates/recruitment/candidate_hired_view.html:202 +msgid "Sync hired candidates to external sources" +msgstr "مزامنة المرشحين المعينين مع مصادر خارجية" + +#: templates/recruitment/candidate_hired_view.html:203 +#: templates/recruitment/candidate_hired_view.html:407 +#, fuzzy +#| msgid "Sources" +msgid "Sync to Sources" +msgstr "مزامنة مع المصادر" + +#: templates/recruitment/candidate_hired_view.html:207 +msgid "Export hired candidates to CSV" +msgstr "تصدير المرشحين المعينين إلى CSV" + +#: templates/recruitment/candidate_hired_view.html:219 +msgid "Congratulations!" +msgstr "تهانينا!" + +#: templates/recruitment/candidate_hired_view.html:220 +msgid "" +"These candidates have successfully completed the hiring process and joined " +"your team." +msgstr "أكمل هؤلاء المرشحون عملية التوظيف بنجاح وانضموا إلى فريقك." + +#: templates/recruitment/candidate_hired_view.html:234 +#, fuzzy +#| msgid "Open Positions" +msgid "Applied Position" +msgstr "المنصب الذي تم التقديم عليه" + +#: templates/recruitment/candidate_hired_view.html:300 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "No candidates have been hired for this position yet." +msgstr "لم يتم تعيين أي مرشحين لهذا المنصب بعد." + +#: templates/recruitment/candidate_hired_view.html:313 +#, fuzzy +#| msgid "New Candidates" +msgid "Hired Candidate Details" +msgstr "تفاصيل المرشح المعين" + +#: templates/recruitment/candidate_hired_view.html:320 +#: templates/recruitment/candidate_interview_view.html:440 +#: templates/recruitment/candidate_interview_view.html:629 +#: templates/recruitment/candidate_offer_view.html:333 +msgid "Loading content..." +msgstr "جاري تحميل المحتوى..." + +#: templates/recruitment/candidate_hired_view.html:333 +#, fuzzy +#| msgid "Sync Status" +msgid "Sync Results" +msgstr "نتائج المزامنة" + +#: templates/recruitment/candidate_hired_view.html:340 +msgid "Syncing candidates..." +msgstr "جاري مزامنة المرشحين..." + +#: templates/recruitment/candidate_hired_view.html:369 +msgid "Syncing hired candidates..." +msgstr "جاري مزامنة المرشحين المعينين..." + +#: templates/recruitment/candidate_hired_view.html:370 +msgid "Please wait while we sync candidates to external sources." +msgstr "يرجى الانتظار بينما نقوم بمزامنة المرشحين مع المصادر الخارجية." + +#: templates/recruitment/candidate_hired_view.html:378 +msgid "Syncing..." +msgstr "جاري المزامنة..." + +#: templates/recruitment/candidate_hired_view.html:402 +msgid "An unexpected error occurred during sync." +msgstr "حدث خطأ غير متوقع أثناء المزامنة." + +#: templates/recruitment/candidate_hired_view.html:419 +#, fuzzy +#| msgid "Summary" +msgid "Sync Summary" +msgstr "ملخص المزامنة" + +#: templates/recruitment/candidate_hired_view.html:422 +#, fuzzy +#| msgid "Sources" +msgid "Total Sources:" +msgstr "إجمالي المصادر:" + +#: templates/recruitment/candidate_hired_view.html:425 +msgid "Successful:" +msgstr "ناجح:" + +#: templates/recruitment/candidate_hired_view.html:428 +#, fuzzy +#| msgid "Failed" +msgid "Failed:" +msgstr "فشل:" + +#: templates/recruitment/candidate_hired_view.html:431 +#, fuzzy +#| msgid "Candidates" +msgid "Candidates Synced:" +msgstr "المرشحون الذين تمت مزامنتهم:" + +#: templates/recruitment/candidate_hired_view.html:439 +#, fuzzy +#| msgid "Core Details" +msgid "Source Details" +msgstr "تفاصيل المصدر" + +#: templates/recruitment/candidate_hired_view.html:457 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Candidates Processed:" +msgstr "المرشحون الذين تمت معالجتهم:" + +#: templates/recruitment/candidate_hired_view.html:461 +#: templates/recruitment/notification_detail.html:71 +#, fuzzy +#| msgid "Duration" +msgid "Duration:" +msgstr "المدة:" + +#: templates/recruitment/candidate_hired_view.html:465 +#: templates/recruitment/notification_confirm_delete.html:21 +#, fuzzy +#| msgid "Error Message" +msgid "Message:" +msgstr "الرسالة:" + +#: templates/recruitment/candidate_hired_view.html:494 +msgid "Sync task failed" +msgstr "فشلت مهمة المزامنة" + +#: templates/recruitment/candidate_hired_view.html:503 +msgid "Failed to check sync status" +msgstr "فشل التحقق من حالة المزامنة" + +#: templates/recruitment/candidate_hired_view.html:510 +msgid "Sync timed out after 5 minutes" +msgstr "انتهت مهلة المزامنة بعد 5 دقائق" + +#: templates/recruitment/candidate_hired_view.html:521 +msgid "Sync in progress..." +msgstr "المزامنة قيد التقدم..." + +#: templates/recruitment/candidate_hired_view.html:532 +#, fuzzy +#| msgid "Failed" +msgid "Sync Failed" +msgstr "فشلت المزامنة" + +#: templates/recruitment/candidate_interview_view.html:177 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Management" +msgstr "إدارة المقابلات" + +#: templates/recruitment/candidate_interview_view.html:180 +msgid "Candidates in Interview Stage:" +msgstr "المرشحون في مرحلة المقابلة:" + +#: templates/recruitment/candidate_interview_view.html:186 +msgid "Export interview candidates to CSV" +msgstr "تصدير مرشحي المقابلة إلى CSV" + +#: templates/recruitment/candidate_interview_view.html:215 +#, fuzzy +#| msgid "Offer" +msgid "To Offer" +msgstr "إلى العرض" + +#: templates/recruitment/candidate_interview_view.html:218 +#, fuzzy +#| msgid "Exam" +msgid "To Exam" +msgstr "إلى الاختبار" + +#: templates/recruitment/candidate_interview_view.html:232 +#, fuzzy +#| msgid "Interview" +msgid "Schedule Interviews" +msgstr "جدولة المقابلات" + +#: templates/recruitment/candidate_interview_view.html:240 +#, fuzzy +#| msgid "Participant Video" +msgid "Manage Participants" +msgstr "إدارة المشاركين" + +#: templates/recruitment/candidate_interview_view.html:265 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting Date" +msgstr "تاريخ الاجتماع" + +#: templates/recruitment/candidate_interview_view.html:267 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Status" +msgstr "حالة الاجتماع" + +#: templates/recruitment/candidate_interview_view.html:268 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Result" +msgstr "نتيجة المقابلة" + +#: templates/recruitment/candidate_interview_view.html:300 +msgid "Minutes" +msgstr "دقائق" + +#: templates/recruitment/candidate_interview_view.html:421 +msgid "No candidates are currently in the Interview stage for this job." +msgstr "لا يوجد مرشحون حاليًا في مرحلة المقابلة لهذه الوظيفة." + +#: templates/recruitment/candidate_interview_view.html:433 +#: templates/recruitment/candidate_interview_view.html:633 +#: templates/recruitment/candidate_offer_view.html:326 +msgid "Candidate Details / Bulk Action Form" +msgstr "تفاصيل المرشح / نموذج الإجراءات المجمعة" + +#: templates/recruitment/candidate_interview_view.html:453 +msgid "Manage all participants" +msgstr "إدارة جميع المشاركين" + +#: templates/recruitment/candidate_interview_view.html:476 +#: templates/recruitment/candidate_interview_view.html:501 +msgid "Users" +msgstr "المستخدمون" + +#: templates/recruitment/candidate_interview_view.html:530 +msgid "Loading email form..." +msgstr "جاري تحميل نموذج البريد الإلكتروني..." + +#: templates/recruitment/candidate_list.html:193 +msgid "Candidate Profiles" +msgstr "ملفات المرشحين الشخصية" + +#: templates/recruitment/candidate_list.html:197 +msgid "Add New Candidate" +msgstr "إضافة مرشح جديد" + +#: templates/recruitment/candidate_list.html:221 +msgid "Filter by Job" +msgstr "تصفية حسب الوظيفة" + +#: templates/recruitment/candidate_list.html:274 +msgid "Major" +msgstr "التخصص" + +#: templates/recruitment/candidate_list.html:277 +#, fuzzy +#| msgid "Created at" +msgid "created At" +msgstr "تم الإنشاء في" + +#: templates/recruitment/candidate_list.html:401 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidate profiles found" +msgstr "لم يتم العثور على ملفات مرشحين شخصية" + +#: templates/recruitment/candidate_list.html:402 +#, fuzzy +#| msgid "Start by adding a new profile or adjusting your search filters." +msgid "Create your first candidate profile or adjust your filters." +msgstr "أنشئ أول ملف شخصي لمرشح أو عدّل عوامل التصفية الخاصة بك." + +#: templates/recruitment/candidate_list.html:405 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add Candidate" +msgstr "إضافة مرشح" + +#: templates/recruitment/candidate_offer_view.html:176 +#, fuzzy +#| msgid "Form Management" +msgid "Offer Management" +msgstr "إدارة العروض" + +#: templates/recruitment/candidate_offer_view.html:179 +msgid "Candidates in Offer Stage:" +msgstr "المرشحون في مرحلة العرض:" + +#: templates/recruitment/candidate_offer_view.html:185 +msgid "Export offer candidates to CSV" +msgstr "تصدير مرشحي العروض إلى CSV" + +#: templates/recruitment/candidate_offer_view.html:213 +msgid "To Hired" +msgstr "إلى التعيين" + +#: templates/recruitment/candidate_offer_view.html:216 +#, fuzzy +#| msgid "Rejected" +msgid "To Rejected" +msgstr "إلى الرفض" + +#: templates/recruitment/candidate_offer_view.html:313 +msgid "No candidates are currently in the Offer stage for this job." +msgstr "لا يوجد مرشحون حاليًا في مرحلة العرض لهذه الوظيفة." + +#: templates/recruitment/candidate_screening_view.html:224 +msgid "Applicant Screening" +msgstr "فحص المتقدمين" + +#: templates/recruitment/candidate_screening_view.html:227 +msgid "Job:" +msgstr "الوظيفة:" + +#: templates/recruitment/candidate_screening_view.html:234 +msgid "Export screening candidates to CSV" +msgstr "تصدير مرشحي الفرز إلى CSV" + +#: templates/recruitment/candidate_screening_view.html:249 +msgid "AI Scoring & Top Candidate Filter" +msgstr "تسجيل النقاط بالذكاء الاصطناعي وتصفية أفضل المرشحين" + +#: templates/recruitment/candidate_screening_view.html:257 +msgid "Min AI Score" +msgstr "الحد الأدنى لنقاط الذكاء الاصطناعي" + +#: templates/recruitment/candidate_screening_view.html:266 +msgid "Min Years Exp" +msgstr "الحد الأدنى لسنوات الخبرة" + +#: templates/recruitment/candidate_screening_view.html:278 +msgid "Any Rating" +msgstr "أي تقييم" + +#: templates/recruitment/candidate_screening_view.html:280 +msgid "Highly Qualified" +msgstr "مؤهل عالي" + +#: templates/recruitment/candidate_screening_view.html:283 +msgid "Qualified" +msgstr "مؤهل" + +#: templates/recruitment/candidate_screening_view.html:286 +msgid "Partially Qualified" +msgstr "مؤهل جزئي" + +#: templates/recruitment/candidate_screening_view.html:289 +msgid "Not Qualified" +msgstr "غير مؤهل" + +#: templates/recruitment/candidate_screening_view.html:296 +#, fuzzy +#| msgid "New Candidates" +msgid "Top N Candidates" +msgstr "أفضل N من المرشحين" + +#: templates/recruitment/candidate_screening_view.html:304 +msgid "Update Filters" +msgstr "تصفية" + +#: templates/recruitment/candidate_screening_view.html:333 +msgid "Exam Stage" +msgstr "مرحلة الاختبار" + +#: templates/recruitment/candidate_screening_view.html:373 +msgid "Is Qualified?" +msgstr "هل هو مؤهل؟" + +#: templates/recruitment/candidate_screening_view.html:376 +msgid "Professional Category" +msgstr "الفئة المهنية" + +#: templates/recruitment/candidate_screening_view.html:379 +msgid "Top 3 Skills" +msgstr "أفضل 3 مهارات" + +#: templates/recruitment/candidate_screening_view.html:422 +msgid "AI scoring.." +msgstr "تسجيل النقاط بالذكاء الاصطناعي..." + +#: templates/recruitment/candidate_screening_view.html:469 +msgid "No candidates match the current stage and filter criteria." +msgstr "لا يوجد مرشحون يطابقون المرحلة الحالية ومعايير التصفية." + +#: templates/recruitment/candidate_screening_view.html:483 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Candidate Criteria Review" +msgstr "مراجعة معايير المرشح" + +#: templates/recruitment/candidate_update.html:92 +msgid "Update Candidate:" +msgstr "تحديث المرشح:" + +#: templates/recruitment/candidate_update.html:94 +msgid "Edit candidate information and details" +msgstr "تعديل معلومات المرشح وتفاصيله" + +#: templates/recruitment/candidate_update.html:102 +msgid "View Candidate" +msgstr "عرض المرشح" + +#: templates/recruitment/candidate_update.html:116 +msgid "Candidate Form" +msgstr "نموذج المرشح" + +#: templates/recruitment/candidate_update.html:135 +msgid "Update Candidate" +msgstr "تحديث المرشح" + +#: templates/recruitment/dashboard.html:4 +#, fuzzy +#| msgid "Dashboard" +msgid "Recruitment Dashboard" +msgstr "لوحة تحكم التوظيف" + +#: templates/recruitment/dashboard.html:123 +msgid "Recruitment Analytics" +msgstr "تحليلات التوظيف" + +#: templates/recruitment/dashboard.html:133 +msgid "Data Scope: " +msgstr "نطاق البيانات: " + +#: templates/recruitment/dashboard.html:135 +msgid "Data Scope: All Jobs" +msgstr "نطاق البيانات: جميع الوظائف" + +#: templates/recruitment/dashboard.html:140 +#, fuzzy +#| msgid "Edit Job" +msgid "Filter Job:" +msgstr "تصفية الوظيفة:" + +#: templates/recruitment/dashboard.html:142 +msgid "All Jobs (Default View)" +msgstr "جميع الوظائف (العرض الافتراضي)" + +#: templates/recruitment/dashboard.html:170 +msgid "Daily Candidate Applications Trend" +msgstr "اتجاه طلبات المرشحين اليومية" + +#: templates/recruitment/dashboard.html:184 +msgid "Top 5 Application Volume" +msgstr "أعلى 5 حجوم طلبات" + +#: templates/recruitment/dashboard.html:200 +msgid "Pipeline Funnel: " +msgstr "مجموع المتقدمين حسب مسار التوظيف: " + +#: templates/recruitment/dashboard.html:202 +msgid "Total Pipeline Funnel (All Jobs)" +msgstr "مجموع المتقدمين حسب مسار التوظيف لجميع الوظائف" + +#: templates/recruitment/dashboard.html:216 +msgid "Time-to-Hire Target Check" +msgstr "فحص هدف وقت التوظيف" + +#: templates/recruitment/dashboard.html:231 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidates From Each Sources" +msgstr "المرشحون من كل مصدر" + +#: templates/recruitment/dashboard.html:278 +msgid "Top 5 Most Applied Jobs" +msgstr "أعلى 5 وظائف من حيث عدد المتقدمين" + +#: templates/recruitment/dashboard.html:286 +#, fuzzy +#| msgid "Applications" +msgid "Total Applications" +msgstr "إجمالي الطلبات" + +#: templates/recruitment/dashboard.html:338 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Count" +msgstr "عدد المرشحين" + +#: templates/recruitment/dashboard.html:406 +#, fuzzy +#| msgid "Create Job" +msgid "Current Job" +msgstr "الوظيفة الحالية" + +#: templates/recruitment/dashboard.html:431 +msgid "Daily Applications (Last 30 Days)" +msgstr "الطلبات اليومية (آخر 30 يومًا)" + +#: templates/recruitment/dashboard.html:446 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:598 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:639 +#, fuzzy +#| msgid "End Date" +msgid "Date" +msgstr "التاريخ" + +#: templates/recruitment/dashboard.html:451 +msgid "New Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/notification_confirm_all_read.html:4 +msgid "Mark All as Read" +msgstr "وضع علامة مقروء على الكل" + +#: templates/recruitment/notification_confirm_all_read.html:22 +msgid "What this will do" +msgstr "ماذا سيفعل هذا" + +#: templates/recruitment/notification_confirm_all_read.html:25 +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| "                                    This will mark %(count)s unread " +#| "notification as read.\n" +#| "                                " +#| msgid_plural "" +#| "\n" +#| "                                    This will mark all %(count)s unread " +#| "notifications as read.\n" +#| "                                " +msgid "" +"\n" +" This will mark %(count)s unread " +"notification as read.\n" +" " +msgid_plural "" +"\n" +" This will mark all %(count)s unread " +"notifications as read.\n" +" " +msgstr[0] "" +"\n" +"سيتم وضع علامة مقروء على إشعار واحد غير مقروء." +msgstr[1] "" +"\n" +"سيتم وضع علامة مقروء على جميع الإشعارات %(count)s غير المقروءة." +msgstr[2] "" +"\n" +"سيتم وضع علامة مقروء على جميع الإشعارات %(count)s غير المقروءة." +msgstr[3] "" +"\n" +"سيتم وضع علامة مقروء على جميع الإشعارات %(count)s غير المقروءة." +msgstr[4] "" +"\n" +"سيتم وضع علامة مقروء على جميع الإشعارات %(count)s غير المقروءة." +msgstr[5] "" +"\n" +"سيتم وضع علامة مقروء على جميع الإشعارات %(count)s غير المقروءة." + +#: templates/recruitment/notification_confirm_all_read.html:32 +msgid "" +"You can still view all notifications in your notification list, but they " +"won't appear as unread." +msgstr "" +"لا يزال بإمكانك عرض جميع الإشعارات في قائمة الإشعارات الخاصة بك، لكنها لن " +"تظهر كغير مقروءة." + +#: templates/recruitment/notification_confirm_all_read.html:38 +msgid "All caught up!" +msgstr "لقد انتهيت من كل شيء!" + +#: templates/recruitment/notification_confirm_all_read.html:41 +msgid "You don't have any unread notifications to mark as read." +msgstr "ليس لديك أي إشعارات غير مقروءة لوضع علامة مقروء عليها." + +#: templates/recruitment/notification_confirm_all_read.html:50 +msgid "Yes, Mark All as Read" +msgstr "نعم، وضع علامة مقروء على الكل" + +#: templates/recruitment/notification_confirm_all_read.html:58 +#: templates/recruitment/notification_detail.html:18 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Notifications" +msgstr "العودة إلى الإشعارات" + +#: templates/recruitment/notification_confirm_delete.html:4 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Delete Notification" +msgstr "حذف الإشعار" + +#: templates/recruitment/notification_confirm_delete.html:20 +msgid "Notification Preview" +msgstr "معاينة الإشعار" + +#: templates/recruitment/notification_confirm_delete.html:30 +#, fuzzy +#| msgid "Delete" +msgid "Yes, Delete" +msgstr "نعم، حذف" + +#: templates/recruitment/notification_detail.html:4 +#: templates/recruitment/notification_detail.html:12 +#, fuzzy +#| msgid "Meeting Details" +msgid "Notification Details" +msgstr "تفاصيل الإشعار" + +#: templates/recruitment/notification_detail.html:14 +msgid "View notification details and manage your preferences" +msgstr "عرض تفاصيل الإشعار وإدارة تفضيلاتك" + +#: templates/recruitment/notification_detail.html:47 +#: templates/recruitment/notification_detail.html:132 +msgid "Mark as Read" +msgstr "وضع علامة مقروء" + +#: templates/recruitment/notification_detail.html:51 +#: templates/recruitment/notification_detail.html:136 +msgid "Mark as Unread" +msgstr "وضع علامة غير مقروء" + +#: templates/recruitment/notification_detail.html:65 +msgid "Topic:" +msgstr "الموضوع:" + +#: templates/recruitment/notification_detail.html:68 +#, fuzzy +#| msgid "Start Time" +msgid "Start Time:" +msgstr "وقت البدء:" + +#: templates/recruitment/notification_detail.html:75 +#, fuzzy +#| msgid "Join Meeting" +msgid "View Meeting" +msgstr "عرض الاجتماع" + +#: templates/recruitment/notification_detail.html:84 +#: templates/recruitment/notification_detail.html:175 +#, fuzzy +#| msgid "Scheduled" +msgid "Scheduled For" +msgstr "مجدول لـ" + +#: templates/recruitment/notification_detail.html:95 +#: templates/recruitment/notification_detail.html:182 +msgid "Delivery Attempts" +msgstr "محاولات التسليم" + +#: templates/recruitment/notification_detail.html:98 +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| "                                        This notification has been " +#| "attempted %(count)s time.\n" +#| "                                    " +#| msgid_plural "" +#| "\n" +#| "                                        This notification has been " +#| "attempted %(count)s times.\n" +#| "                                    " +msgid "" +"\n" +" This notification has been attempted " +"%(count)s time.\n" +" " +msgid_plural "" +"\n" +" This notification has been attempted " +"%(count)s times.\n" +" " +msgstr[0] "" +"\n" +"تمت محاولة إرسال هذا الإشعار مرة واحدة." +msgstr[1] "" +"\n" +"تمت محاولة إرسال هذا الإشعار %(count)s مرات." +msgstr[2] "" +"\n" +"تمت محاولة إرسال هذا الإشعار %(count)s مرات." +msgstr[3] "" +"\n" +"تمت محاولة إرسال هذا الإشعار %(count)s مرات." +msgstr[4] "" +"\n" +"تمت محاولة إرسال هذا الإشعار %(count)s مرات." +msgstr[5] "" +"\n" +"تمت محاولة إرسال هذا الإشعار %(count)s مرات." + +#: templates/recruitment/notification_detail.html:110 +#, fuzzy +#| msgid "Error" +msgid "Last Error" +msgstr "آخر خطأ" + +#: templates/recruitment/notification_detail.html:150 +#, fuzzy +#| msgid "Join Information" +msgid "Information" +msgstr "المعلومات" + +#: templates/recruitment/notification_list.html:15 +#, fuzzy, python-format +#| msgid "" +#| "\n" +#| "                    %(count)s notification\n" +#| "                " +#| msgid_plural "" +#| "\n" +#| "                    %(count)s notifications\n" +#| "                " +msgid "" +"\n" +" %(count)s notification\n" +" " +msgid_plural "" +"\n" +" %(count)s notifications\n" +" " +msgstr[0] "" +"\n" +"إشعار واحد" +msgstr[1] "" +"\n" +"%(count)s إشعارات" +msgstr[2] "" +"\n" +"%(count)s إشعارات" +msgstr[3] "" +"\n" +"%(count)s إشعارات" +msgstr[4] "" +"\n" +"%(count)s إشعارات" +msgstr[5] "" +"\n" +"%(count)s إشعارات" + +#: templates/recruitment/notification_list.html:26 +msgid "Mark All Read" +msgstr "وضع علامة مقروء على الكل" + +#: templates/recruitment/notification_list.html:39 +#, fuzzy +#| msgid "Status" +msgid "All Status" +msgstr "جميع الحالات" + +#: templates/recruitment/notification_list.html:40 +#: templates/recruitment/notification_list.html:82 +msgid "Unread" +msgstr "غير مقروء" + +#: templates/recruitment/notification_list.html:48 +msgid "All Types" +msgstr "جميع الأنواع" + +#: templates/recruitment/notification_list.html:57 +msgid "Filter" +msgstr "تصفية" + +#: templates/recruitment/notification_list.html:74 +msgid "Total Notifications" +msgstr "إجمالي الإشعارات" + +#: templates/recruitment/notification_list.html:90 +msgid "Email Notifications" +msgstr "إشعارات البريد الإلكتروني" + +#: templates/recruitment/notification_list.html:122 +#, fuzzy +#| msgid "Create Zoom Meeting" +msgid "Related to meeting:" +msgstr "متعلق بالاجتماع:" + +#: templates/recruitment/notification_list.html:130 +msgid "Mark as read" +msgstr "وضع علامة مقروء" + +#: templates/recruitment/notification_list.html:136 +msgid "Mark as unread" +msgstr "وضع علامة غير مقروء" + +#: templates/recruitment/notification_list.html:142 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Delete notification" +msgstr "حذف الإشعار" + +#: templates/recruitment/notification_list.html:155 +msgid "Notifications pagination" +msgstr "ترقيم صفحات الإشعارات" + +#: templates/recruitment/notification_list.html:190 +#, fuzzy +#| msgid "No candidates found." +msgid "No notifications found" +msgstr "لم يتم العثور على إشعارات" + +#: templates/recruitment/notification_list.html:193 +msgid "Try adjusting your filters to see more notifications." +msgstr "حاول تعديل عوامل التصفية الخاصة بك لرؤية المزيد من الإشعارات." + +#: templates/recruitment/notification_list.html:195 +#, fuzzy +#| msgid "You haven't created any form templates yet." +msgid "You don't have any notifications yet." +msgstr "ليس لديك أي إشعارات بعد." + +#: templates/recruitment/partials/_candidate_table.html:10 +msgid "Name / Contact" +msgstr "الاسم / جهة الاتصال" + +#: templates/recruitment/partials/_candidate_table.html:64 +msgid "View Details and Score Breakdown" +msgstr "عرض التفاصيل وتفصيل النقاط" + +#: templates/recruitment/partials/_candidate_table.html:75 +#, fuzzy +#| msgid "Create Candidate" +msgid "Mark as Potential Candidate" +msgstr "وضع علامة مرشح محتمل" + +#: templates/recruitment/partials/_candidate_table.html:83 +msgid "Move to Next Stage" +msgstr "الانتقال إلى المرحلة التالية" + +#: templates/recruitment/partials/_candidate_table.html:92 +msgid "Move to" +msgstr "الانتقال إلى" + +#: templates/recruitment/partials/_candidate_table.html:102 +#, fuzzy +#| msgid "Exam Status" +msgid "Update Exam Status" +msgstr "تحديث حالة الاختبار" + +#: templates/recruitment/partials/_candidate_table.html:120 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates found in this list." +msgstr "لم يتم العثور على مرشحين في هذه القائمة." + +#: templates/recruitment/partials/_candidate_table.html:122 +msgid "" +"Adjust your 'Top N' filter in the controls above or check the All Applicants " +"list." +msgstr "" +"عدّل عامل التصفية 'أفضل N' في عناصر التحكم أعلاه أو تحقق من قائمة جميع " +"المتقدمين." + +#: templates/recruitment/partials/_guage_chart.html:36 +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Days" +msgstr "أيام" + +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Target:" +msgstr "الهدف:" + +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Max Scale:" +msgstr "الحد الأقصى للمقياس:" + +#: templates/recruitment/partials/ai_overview_breadcromb.html:25 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:9 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:11 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:9 +msgid "Home" +msgstr "الرئيسية" + +#: templates/recruitment/partials/ai_overview_breadcromb.html:113 +#, fuzzy +#| msgid "Job Overview" +msgid "AI Overview" +msgstr "نظرة عامة بالذكاء الاصطناعي" + +#: templates/recruitment/partials/stats_cards.html:10 +#, fuzzy +#| msgid "Contact & Job" +msgid "Total Jobs" +msgstr "إجمالي الوظائف" + +#: templates/recruitment/partials/stats_cards.html:13 +msgid "All Active & Drafted Positions (Global)" +msgstr "جميع المناصب النشطة والمسودة (عالمي)" + +#: templates/recruitment/partials/stats_cards.html:19 +msgid "Active Jobs" +msgstr "الوظائف النشطة" + +#: templates/recruitment/partials/stats_cards.html:22 +msgid "Currently Open Requisitions (Scoped)" +msgstr "الطلبات المفتوحة حاليًا (نطاق محدد)" + +#: templates/recruitment/partials/stats_cards.html:31 +msgid "Total Profiles in Current Scope" +msgstr "إجمالي الملفات الشخصية في النطاق الحالي" + +#: templates/recruitment/partials/stats_cards.html:40 +msgid "Total Slots to be Filled (Scoped)" +msgstr "إجمالي الأماكن المراد شغلها (نطاق محدد)" + +#: templates/recruitment/partials/stats_cards.html:46 +msgid "Total Participants" +msgstr "إجمالي المشاركين" + +#: templates/recruitment/partials/stats_cards.html:49 +msgid "Total Recruiters/Interviewers (Global)" +msgstr "إجمالي موظفي التوظيف/المحاورين (عالمي)" + +#: templates/recruitment/partials/stats_cards.html:55 +#, fuzzy +#| msgid "LinkedIn Connected" +msgid "LinkedIn Posts" +msgstr "منشورات LinkedIn" + +#: templates/recruitment/partials/stats_cards.html:58 +msgid "Total Job Posts Sent to LinkedIn (Global)" +msgstr "إجمالي إعلانات الوظائف المرسلة إلى LinkedIn (عالمي)" + +#: templates/recruitment/partials/stats_cards.html:63 +msgid "New Apps (7 Days)" +msgstr "طلبات جديدة (7 أيام)" + +#: templates/recruitment/partials/stats_cards.html:66 +msgid "Incoming applications last week" +msgstr "الطلبات الواردة في الأسبوع الماضي" + +#: templates/recruitment/partials/stats_cards.html:71 +msgid "Avg. Apps per Job" +msgstr "متوسط الطلبات لكل وظيفة" + +#: templates/recruitment/partials/stats_cards.html:74 +msgid "Average Applications per Job (Scoped)" +msgstr "متوسط الطلبات لكل وظيفة (نطاق محدد)" + +#: templates/recruitment/partials/stats_cards.html:81 +#, fuzzy +#| msgid "Timezone" +msgid "Time-to-Hire" +msgstr "وقت التوظيف" + +#: templates/recruitment/partials/stats_cards.html:84 +msgid "Avg. Days (Application to Hired)" +msgstr "متوسط الأيام (من تقديم الطلب إلى التوظيف)" + +#: templates/recruitment/partials/stats_cards.html:89 +msgid "Avg. Match Score" +msgstr "متوسط نقاط التوافق" + +#: templates/recruitment/partials/stats_cards.html:92 +msgid "Average AI Score (Current Scope)" +msgstr "متوسط نقاط الذكاء الاصطناعي (النطاق الحالي)" + +#: templates/recruitment/partials/stats_cards.html:100 +#, fuzzy, python-format +#| msgid "Score ≥ 75% Profiles" +msgid "Score ≥ 75%% Profiles" +msgstr "الملفات الشخصية بنقاط ≥ 75%" + +#: templates/recruitment/partials/stats_cards.html:105 +#, fuzzy +#| msgid "Meetings" +msgid "Meetings This Week" +msgstr "اجتماعات هذا الأسبوع" + +#: templates/recruitment/partials/stats_cards.html:108 +msgid "Scheduled Interviews (Current Week)" +msgstr "المقابلات المجدولة (الأسبوع الحالي)" + +#: templates/recruitment/schedule_meeting_form.html:22 +msgid "" +"This candidate has upcoming interviews. You are updating an existing " +"schedule." +msgstr "هذا المرشح لديه مقابلات قادمة. أنت تقوم بتحديث جدول زمني موجود." + +#: templates/recruitment/schedule_meeting_form.html:27 +#, fuzzy +#| msgid "Candidates" +msgid "Back to Candidates" +msgstr "العودة إلى المرشحين" + +#: templates/recruitment/schedule_meeting_form.html:49 +msgid "" +"Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left " +"empty." +msgstr "" +"سيكون الموضوع الافتراضي هو 'مقابلة: [عنوان الوظيفة] مع [اسم المرشح]' إذا تُرك " +"فارغًا." + +#: templates/recruitment/schedule_meeting_form.html:66 +msgid "Please select a date and time for the interview." +msgstr "الرجاء تحديد تاريخ ووقت للمقابلة." + +#: templates/recruitment/training_create.html:107 +msgid "Create New Training Material" +msgstr "إنشاء مادة تدريبية جديدة" + +#: templates/recruitment/training_create.html:109 +msgid "Upload a new document or guide for your team." +msgstr "رفع مستند أو دليل جديد لفريقك." + +#: templates/recruitment/training_create.html:125 +#: templates/recruitment/training_update.html:131 +msgid "Material Details" +msgstr "تفاصيل المادة" + +#: templates/recruitment/training_list.html:132 +msgid "Add New Material" +msgstr "إضافة مادة جديدة" + +#: templates/recruitment/training_list.html:141 +msgid "Search by Title or Creator" +msgstr "البحث حسب العنوان أو المنشئ" + +#: templates/recruitment/training_list.html:170 +#: templates/recruitment/training_list.html:205 +msgid "Created By" +msgstr "أنشأ بواسطة" + +#: templates/recruitment/training_list.html:171 +#, fuzzy +#| msgid "Created" +msgid "Created On" +msgstr "تاريخ الإنشاء" + +#: templates/recruitment/training_list.html:274 +#, fuzzy +#| msgid "No training materials found." +msgid "No training materials found" +msgstr "لم يتم العثور على مواد تدريبية" + +#: templates/recruitment/training_list.html:275 +msgid "It looks like there are no materials yet. Start by adding one!" +msgstr "يبدو أنه لا توجد مواد بعد. ابدأ بإضافة واحدة!" + +#: templates/recruitment/training_list.html:278 +msgid "Create Your First Material" +msgstr "إنشاء أول مادة لك" + +#: templates/recruitment/training_update.html:107 +msgid "Update Training Material:" +msgstr "تحديث المادة التدريبية:" + +#: templates/recruitment/training_update.html:109 +msgid "Edit the details of this training document or guide." +msgstr "تعديل تفاصيل هذا المستند التدريبي أو الدليل." + +#: templates/recruitment/training_update.html:117 +msgid "View Material" +msgstr "عرض المادة" + +#: templates/recruitment/training_update.html:180 +msgid "Update Material" +msgstr "تحديث المادة" + +#: templates/recruitment/training_update.html:182 +msgid "Are you sure you want to delete this material?" +msgstr "هل أنت متأكد من رغبتك في حذف هذه المادة؟" + +#: templates/unfold/components/table.html:43 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/components/table.html:89 +msgid "No data" +msgstr "لا توجد بيانات" + +#: templates/user/admin_settings.html:6 +#, fuzzy +#| msgid "Settings" +msgid "Admin Settings" +msgstr "إعدادات المسؤول" + +#: templates/user/admin_settings.html:149 +msgid "Admin Settings Dashboard" +msgstr "لوحة تحكم إعدادات المسؤول" + +#: templates/user/admin_settings.html:159 +msgid "Staff User List" +msgstr "قائمة مستخدمي الموظفين" + +#: templates/user/admin_settings.html:163 +#, fuzzy +#| msgid "Create New Form" +msgid "Create New User" +msgstr "إنشاء مستخدم جديد" + +#: templates/user/admin_settings.html:177 +#, fuzzy +#| msgid "First Name" +msgid "First Join" +msgstr "أول انضمام" + +#: templates/user/admin_settings.html:178 templates/user/profile.html:178 +msgid "Last Login" +msgstr "آخر تسجيل دخول" + +#: templates/user/admin_settings.html:231 +msgid "Deactivate User" +msgstr "تعطيل المستخدم" + +#: templates/user/admin_settings.html:238 +#, fuzzy +#| msgid "Active Jobs" +msgid "Activate User" +msgstr "تنشيط المستخدم" + +#: templates/user/admin_settings.html:239 +#, fuzzy +#| msgid "Active" +msgid "Activate" +msgstr "تنشيط" + +#: templates/user/admin_settings.html:248 +#, fuzzy +#| msgid "No training materials found." +msgid "No staff users found." +msgstr "لم يتم العثور على مستخدمي موظفين." + +#: templates/user/create_staff.html:6 templates/user/create_staff.html:37 +#: templates/user/create_staff.html:69 +#, fuzzy +#| msgid "Create Material" +msgid "Create Staff User" +msgstr "إنشاء مستخدم موظف" + +#: templates/user/create_staff.html:76 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Settings" +msgstr "العودة إلى الإعدادات" + +#: templates/user/profile.html:5 +#, fuzzy +#| msgid "Profile" +msgid "User Profile" +msgstr "الملف الشخصي للمستخدم" + +#: templates/user/profile.html:142 +msgid "Manage email addresses" +msgstr "إدارة عناوين البريد الإلكتروني" + +#: templates/user/profile.html:157 +msgid "Security" +msgstr "الأمان" + +#: templates/user/profile.html:164 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Change Profile Image" +msgstr "تغيير صورة الملف الشخصي" + +#: templates/user/profile.html:170 +#, fuzzy +#| msgid "Sync Status" +msgid "Account Status" +msgstr "حالة الحساب" + +#: templates/user/profile.html:173 +msgid "Username" +msgstr "اسم المستخدم" + +#: templates/user/profile.html:185 +msgid "Date Joined" +msgstr "تاريخ الانضمام" + +#: venv/lib/python3.13/site-packages/_pytest/config/argparsing.py:474 +#, python-format +msgid "ambiguous option: %(option)s could match %(matches)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/_termui_impl.py:608 +#, python-brace-format +msgid "{editor}: Editing failed" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/_termui_impl.py:612 +#, python-brace-format +msgid "{editor}: Editing failed: {e}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1104 +#: venv/lib/python3.13/site-packages/click/core.py:1141 +#, python-brace-format +msgid "{text} {deprecated_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1160 +#: venv/lib/python3.13/site-packages/typer/core.py:633 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:96 +#, fuzzy +#| msgid "Actions" +msgid "Options" +msgstr "الإجراءات" + +#: venv/lib/python3.13/site-packages/click/core.py:1222 +#, python-brace-format +msgid "Got unexpected extra argument ({args})" +msgid_plural "Got unexpected extra arguments ({args})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/core.py:1241 +msgid "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1425 +#: venv/lib/python3.13/site-packages/typer/core.py:249 +msgid "Aborted!" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1799 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:97 +msgid "Commands" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1830 +msgid "Missing command." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1908 +msgid "No such command {name!r}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2332 +msgid "Value must be an iterable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2355 +#, python-brace-format +msgid "Takes {nargs} values but 1 was given." +msgid_plural "Takes {nargs} values but {len} were given." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/core.py:2505 +msgid "" +"DeprecationWarning: The {param_type} {name!r} is deprecated.{extra_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2956 +#: venv/lib/python3.13/site-packages/typer/core.py:553 +#, python-brace-format +msgid "env var: {var}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2959 +#: venv/lib/python3.13/site-packages/typer/core.py:366 +#: venv/lib/python3.13/site-packages/typer/core.py:574 +#, python-brace-format +msgid "default: {default}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:3023 +#: venv/lib/python3.13/site-packages/typer/core.py:114 +msgid "(dynamic)" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:465 +#, python-format +msgid "%(prog)s, version %(version)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:522 +msgid "Show the version and exit." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:548 +msgid "Show this message and exit." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:50 +#: venv/lib/python3.13/site-packages/click/exceptions.py:89 +#, fuzzy, python-brace-format +#| msgid "Error Message" +msgid "Error: {message}" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:81 +#, python-brace-format +msgid "Try '{command} {option}' for help." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:130 +#, python-brace-format +msgid "Invalid value: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:132 +#, python-brace-format +msgid "Invalid value for {param_hint}: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:190 +msgid "Missing argument" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:192 +#, fuzzy +#| msgid "Meeting Information" +msgid "Missing option" +msgstr "معلومات الاجتماع" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:194 +msgid "Missing parameter" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:196 +#, python-brace-format +msgid "Missing {param_type}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:203 +#, python-brace-format +msgid "Missing parameter: {param_name}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:223 +#, python-brace-format +msgid "No such option: {name}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:235 +#, python-brace-format +msgid "Did you mean {possibility}?" +msgid_plural "(Possible options: {possibilities})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:282 +msgid "unknown error" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:289 +msgid "Could not open file {filename!r}: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/formatting.py:156 +#, fuzzy +#| msgid "Stage:" +msgid "Usage:" +msgstr "المرحلة:" + +#: venv/lib/python3.13/site-packages/click/parser.py:199 +msgid "Argument {name!r} takes {nargs} values." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/parser.py:381 +msgid "Option {name!r} does not take a value." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/parser.py:444 +msgid "Option {name!r} requires an argument." +msgid_plural "Option {name!r} requires {nargs} arguments." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/shell_completion.py:332 +msgid "Shell completion is not supported for Bash versions older than 4.4." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/shell_completion.py:339 +msgid "Couldn't detect Bash version, shell completion is not supported." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:162 +#, fuzzy +#| msgid "Meeting Information" +msgid "Repeat for confirmation" +msgstr "معلومات الاجتماع" + +#: venv/lib/python3.13/site-packages/click/termui.py:178 +msgid "Error: The value you entered was invalid." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:180 +#, fuzzy, python-brace-format +#| msgid "Error Message" +msgid "Error: {e.message}" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/click/termui.py:191 +msgid "Error: The two entered values do not match." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:247 +msgid "Error: invalid input" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:866 +msgid "Press any key to continue..." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:332 +#, python-brace-format +msgid "" +"Choose from:\n" +"\t{choices}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:369 +msgid "{value!r} is not {choice}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/types.py:460 +msgid "{value!r} does not match the format {format}." +msgid_plural "{value!r} does not match the formats {formats}." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/types.py:482 +msgid "{value!r} is not a valid {number_type}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:538 +#, python-brace-format +msgid "{value} is not in the range {range}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:719 +msgid "{value!r} is not a valid boolean. Recognized values: {states}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:747 +msgid "{value!r} is not a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:937 +#, fuzzy +#| msgid "Profile" +msgid "file" +msgstr "الملف الشخصي" + +#: venv/lib/python3.13/site-packages/click/types.py:939 +msgid "directory" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:941 +msgid "path" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:988 +msgid "{name} {filename!r} does not exist." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:997 +msgid "{name} {filename!r} is a file." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1005 +msgid "{name} {filename!r} is a directory." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1014 +msgid "{name} {filename!r} is not readable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1023 +msgid "{name} {filename!r} is not writable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1032 +msgid "{name} {filename!r} is not executable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1099 +#, python-brace-format +msgid "{len_type} values are required, but {len_value} was given." +msgid_plural "{len_type} values are required, but {len_value} were given." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:240 +msgid "RoW" +msgstr "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:240 +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:243 +msgid "GLO" +msgstr "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:243 +msgid "RoE" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/messages/apps.py:16 +#, fuzzy +#| msgid "Error Message" +msgid "Messages" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/django/contrib/sitemaps/apps.py:8 +msgid "Site Maps" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/syndication/apps.py:7 +#, fuzzy +#| msgid "Application" +msgid "Syndication" +msgstr "التقديم" + +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: venv/lib/python3.13/site-packages/django/core/paginator.py:30 +msgid "…" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:32 +msgid "That page number is not an integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:33 +msgid "That page number is less than 1" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:34 +msgid "That page contains no results" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:22 +msgid "Enter a valid value." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:70 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a valid domain name." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:153 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:775 +msgid "Enter a valid URL." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:200 +msgid "Enter a valid integer." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:211 +msgid "Enter a valid email address." +msgstr "" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: venv/lib/python3.13/site-packages/django/core/validators.py:289 +msgid "" +"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:297 +msgid "" +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:309 +#: venv/lib/python3.13/site-packages/django/core/validators.py:318 +#: venv/lib/python3.13/site-packages/django/core/validators.py:332 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2220 +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:311 +msgid "IPv4" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:320 +#: venv/lib/python3.13/site-packages/django/utils/ipv6.py:43 +msgid "IPv6" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:334 +msgid "IPv4 or IPv6" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:375 +msgid "Enter only digits separated by commas." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:381 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:416 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:425 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:434 +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:441 +#, python-format +msgid "" +"Ensure this value is a multiple of step size %(limit_value)s, starting from " +"%(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:473 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:491 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:514 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:366 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:405 +#, fuzzy +#| msgid "Enter phone number" +msgid "Enter a number." +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:516 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:521 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:526 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:597 +#, python-format +msgid "" +"File extension “%(extension)s” is not allowed. Allowed extensions are: " +"%(allowed_extensions)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:659 +msgid "Null characters are not allowed." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/base.py:1600 +#: venv/lib/python3.13/site-packages/django/forms/models.py:908 +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:108 +msgid "and" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/base.py:1602 +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/constraints.py:22 +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:134 +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:135 +msgid "This field cannot be null." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:136 +msgid "This field cannot be blank." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:137 +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:141 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:180 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1162 +#, python-format +msgid "“%(value)s” value must be either True or False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1163 +#, python-format +msgid "“%(value)s” value must be either True, False, or None." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1165 +msgid "Boolean (Either True or False)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1215 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1217 +msgid "String (unlimited)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1326 +#, fuzzy +#| msgid "Comma-separated list of trusted IP addresses" +msgid "Comma-separated integers" +msgstr "قائمة عناوين IP الموثوقة مفصولة بفواصل" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1427 +#, python-format +msgid "" +"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1431 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1566 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1435 +msgid "Date (without time)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1562 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD " +"HH:MM[:ss[.uuuuuu]][TZ] format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1570 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1575 +msgid "Date (with time)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1702 +#, python-format +msgid "“%(value)s” value must be a decimal number." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1704 +msgid "Decimal number" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1864 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in [DD] " +"[[HH:]MM:]ss[.uuuuuu] format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1920 +#, fuzzy +#| msgid "IP Address" +msgid "Email address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1945 +#, fuzzy +#| msgid "File" +msgid "File path" +msgstr "ملف" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2023 +#, python-format +msgid "“%(value)s” value must be a float." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2025 +#, fuzzy +#| msgid "Enter phone number" +msgid "Floating point number" +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2065 +#, python-format +msgid "“%(value)s” value must be an integer." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2067 +#, fuzzy +#| msgid "Interview" +msgid "Integer" +msgstr "المقابلة" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2163 +msgid "Big (8 byte) integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2180 +msgid "Small integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2188 +#, fuzzy +#| msgid "IP Address" +msgid "IPv4 address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2219 +#, fuzzy +#| msgid "IP Address" +msgid "IP address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2310 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2311 +#, python-format +msgid "“%(value)s” value must be either None, True or False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2313 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2364 +msgid "Positive big integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2379 +msgid "Positive integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2394 +msgid "Positive small integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2410 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2446 +msgid "Text" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2526 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2530 +#, python-format +msgid "" +"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2534 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:263 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:279 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:599 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:644 +#, fuzzy +#| msgid "Timezone" +msgid "Time" +msgstr "المنطقة الزمنية" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2642 +msgid "URL" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2666 +msgid "Raw binary data" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2731 +#, python-format +msgid "“%(value)s” is not a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2733 +msgid "Universally unique identifier" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/files.py:420 +msgid "Image" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/json.py:24 +msgid "A JSON object" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/json.py:26 +msgid "Value must be valid JSON." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:979 +#, python-format +msgid "%(model)s instance with %(field)s %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:982 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1276 +msgid "One-to-one relationship" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1333 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1335 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1383 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: venv/lib/python3.13/site-packages/django/forms/boundfield.py:185 +msgid ":?.!" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:95 +msgid "This field is required." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:315 +#, fuzzy +#| msgid "Enter phone number" +msgid "Enter a whole number." +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:486 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1267 +#, fuzzy +#| msgid "Interview Date" +msgid "Enter a valid date." +msgstr "تاريخ المقابلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:509 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1268 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a valid time." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:536 +msgid "Enter a valid date/time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:570 +#, fuzzy +#| msgid "Internal Information" +msgid "Enter a valid duration." +msgstr "المعلومات الداخلية" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:571 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:640 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:641 +msgid "No file was submitted." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:642 +msgid "The submitted file is empty." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:644 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:649 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:717 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:889 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:975 +#: venv/lib/python3.13/site-packages/django/forms/models.py:1592 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:977 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1096 +#: venv/lib/python3.13/site-packages/django/forms/models.py:1590 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a list of values." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1097 +#, fuzzy +#| msgid "Enter template name" +msgid "Enter a complete value." +msgstr "أدخل اسم القالب" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1339 +msgid "Enter a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1369 +msgid "Enter a valid JSON." +msgstr "" + +#. Translators: This is the default suffix added to form field labels +#: venv/lib/python3.13/site-packages/django/forms/forms.py:97 +msgid ":" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/forms.py:239 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:61 +#, python-format +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:65 +#, python-format +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:70 +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:484 +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:491 +msgid "Order" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:901 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:906 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:913 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:922 +msgid "Please correct the duplicate values below." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1359 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1450 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1594 +#, python-format +msgid "“%(pk)s” is not a valid value." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/utils.py:229 +#, python-format +msgid "" +"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:528 +msgid "Currently" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:529 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:63 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:42 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:23 +#, fuzzy +#| msgid "Change Stage" +msgid "Change" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:866 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "Unknown" +msgstr "" + +#. Translators: Please do not add spaces around commas. +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:873 +msgid "yes,no,maybe" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:903 +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:920 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:922 +#, python-format +msgid "%s KB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:924 +#, python-format +msgid "%s MB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:926 +#, python-format +msgid "%s GB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:928 +#, python-format +msgid "%s TB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:930 +#, python-format +msgid "%s PB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:74 +msgid "p.m." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:75 +msgid "a.m." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:80 +msgid "PM" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:81 +msgid "AM" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:153 +msgid "midnight" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:155 +msgid "noon" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:7 +msgid "Monday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:8 +msgid "Tuesday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:9 +msgid "Wednesday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:10 +msgid "Thursday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:11 +msgid "Friday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:12 +msgid "Saturday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:13 +msgid "Sunday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:16 +msgid "Mon" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:17 +msgid "Tue" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:18 +msgid "Wed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:19 +msgid "Thu" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:20 +msgid "Fri" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:21 +#, fuzzy +#| msgid "Status" +msgid "Sat" +msgstr "الحالة" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:22 +msgid "Sun" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:25 +msgid "January" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:26 +msgid "February" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:27 +#, fuzzy +#| msgid "Search" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:28 +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:29 +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:30 +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:31 +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:32 +msgid "August" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:33 +msgid "September" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:34 +msgid "October" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:35 +msgid "November" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:36 +msgid "December" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:39 +msgid "jan" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:40 +msgid "feb" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:41 +#, fuzzy +#| msgid "Summary" +msgid "mar" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:42 +msgid "apr" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:43 +#, fuzzy +#| msgid "Summary" +msgid "may" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:44 +msgid "jun" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:45 +msgid "jul" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:46 +#, fuzzy +#| msgid "ago" +msgid "aug" +msgstr "منذ" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:47 +msgid "sep" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:48 +msgid "oct" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:49 +msgid "nov" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:50 +msgid "dec" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:53 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:54 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:55 +#, fuzzy +#| msgid "Search" +msgctxt "abbrev. month" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:56 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:57 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:58 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:59 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:60 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:61 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:62 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:63 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:64 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:67 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:68 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:69 +#, fuzzy +#| msgid "Search" +msgctxt "alt. month" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:70 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:71 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:72 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:73 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:74 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:75 +msgctxt "alt. month" +msgid "September" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:76 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:77 +msgctxt "alt. month" +msgid "November" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:78 +msgctxt "alt. month" +msgid "December" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/ipv6.py:20 +msgid "This is not a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/text.py:76 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/text.py:287 +#, fuzzy +#| msgid "More" +msgid "or" +msgstr "المزيد" + +#. Translators: This string is used as a separator between list elements +#: venv/lib/python3.13/site-packages/django/utils/text.py:306 +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:135 +msgid ", " +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:8 +#, python-format +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:29 +msgid "Forbidden" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:30 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:34 +msgid "" +"You are seeing this message because this HTTPS site requires a “Referer " +"header” to be sent by your web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:40 +msgid "" +"If you have configured your browser to disable “Referer” headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for “same-" +"origin” requests." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:45 +msgid "" +"If you are using the tag or " +"including the “Referrer-Policy: no-referrer” header, please remove them. The " +"CSRF protection requires the “Referer” header to do strict referer checking. " +"If you’re concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:54 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:60 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for “same-origin” requests." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:66 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:44 +msgid "No year specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:64 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:115 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:214 +msgid "Date out of range" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:94 +msgid "No month specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:147 +msgid "No day specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:194 +msgid "No week specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:353 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:384 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:680 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because " +"%(class_name)s.allow_future is False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:720 +#, python-format +msgid "Invalid date string “%(datestr)s” given format “%(format)s”" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/detail.py:56 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:70 +msgid "Page is not “last”, nor can it be converted to an int." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:77 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:173 +#, python-format +msgid "Empty list and “%(class_name)s.allow_empty” is False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:49 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:51 +#, python-format +msgid "“%(path)s” does not exist" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:68 +#: venv/lib/python3.13/site-packages/django/views/templates/directory_index.html:8 +#: venv/lib/python3.13/site-packages/django/views/templates/directory_index.html:11 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:7 +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:204 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:206 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:208 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not " +"configured any URLs." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:217 +msgid "Django Documentation" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:218 +msgid "Topics, references, & how-to’s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:226 +msgid "Tutorial: A Polling App" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:227 +msgid "Get started with Django" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:235 +msgid "Django Community" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:236 +msgid "Connect, get help, or contribute" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/permissions.py:18 +msgid "You do not have permission to upload files." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/permissions.py:25 +msgid "You must be logged in to upload files." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/validators.py:17 +#, python-format +msgid "File should be at most %(max_size)s MB." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/views.py:41 +msgid "Invalid form data" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/widgets.py:43 +msgid "Check the correct settings.CKEDITOR_5_CONFIGS " +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:72 +msgid "Only POST method is allowed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:86 +msgid "Attachment module is disabled" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:93 +msgid "Only authenticated users are allowed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:99 +msgid "No files were requested" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:140 +msgid "File size exceeds the limit allowed and cannot be saved" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:160 +msgid "Failed to save attachment" +msgstr "" + +#: venv/lib/python3.13/site-packages/isort/main.py:158 +msgid "show this help message and exit" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1311 +#, python-format +msgid "Attempting to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1316 +#, python-format +msgid "Connected to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1334 +#, python-format +msgid "Unable to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/pycountry/tests/test_general.py:184 +msgid "Germany" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/core.py:368 +#: venv/lib/python3.13/site-packages/typer/core.py:583 +msgid "required" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/core.py:630 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:95 +msgid "Arguments" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:89 +#, fuzzy +#| msgid "Created at" +msgid "(deprecated) " +msgstr "تم الإنشاء في" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:90 +msgid "[default: {}]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:91 +msgid "[env var: {}]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:93 +msgid "[required]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:99 +msgid "Aborted." +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:100 +#, python-brace-format +msgid "Try [blue]'{command_path} {help_option}'[/] for help." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/admin.py:40 +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold_list.py:337 +#, fuzzy +#| msgid "Select country" +msgid "Select record" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/admin.py:166 +#, fuzzy +#| msgid "Select country" +msgid "Select action" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:10 +msgid "Collapse" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:20 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:172 +msgid "Value" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:21 +msgid "Default" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:59 +msgid "Code" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:23 +msgid "Modified" +msgstr "تم التعديل" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:65 +msgid "Reset to default" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:46 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:103 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:137 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:168 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:28 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:61 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:105 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:71 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:169 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/text_filters.py:29 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/text_filters.py:60 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_date_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/filter.html:5 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:193 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:234 +msgid "To" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:258 +msgid "Date from" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:274 +msgid "Date to" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html:30 +#, fuzzy +#| msgid "No data" +msgid "Not enough data." +msgstr "لا توجد بيانات" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/array.html:30 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add new item" +msgstr "إضافة مرشح جديد" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:7 +msgid "Paragraph" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:11 +msgid "Underlined" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:15 +msgid "Bold" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:19 +msgid "Italic" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:23 +msgid "Strike" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:35 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:39 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:43 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:47 +msgid "Heading" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:55 +msgid "Quote" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:63 +msgid "Unordered list" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:67 +msgid "Ordered list" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:71 +msgid "Indent increase" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:75 +msgid "Indent decrease" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:83 +msgid "Undo" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:87 +msgid "Redo" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:95 +#, fuzzy +#| msgid "Enter email" +msgid "Enter an URL" +msgstr "أدخل البريد الإلكتروني" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:102 +msgid "Unlink" +msgstr "فك الربط" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/change_form.html:8 +msgid "Object permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:13 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:14 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:9 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:43 +msgid "Object" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:16 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:15 +msgid "Group" +msgstr "تجميع" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:7 +msgid "Group permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:67 +#, fuzzy +#| msgid "Manage" +msgid "Manage group" +msgstr "إدارة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:7 +msgid "User permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:67 +#, fuzzy +#| msgid "Manage" +msgid "Manage user" +msgstr "إدارة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_form.html:8 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html:4 +msgid "Export" +msgstr "تصدير" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html:4 +msgid "Import" +msgstr "استيراد" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:18 +#, python-format +msgid "" +"\n" +" Export %(len)s selected item.\n" +" " +msgid_plural "" +"\n" +" Export %(len)s selected items.\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:37 +msgid "This exporter will export the following fields" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html:10 +msgid "" +"Below is a preview of data to be imported. If you are satisfied with the " +"results, click 'Confirm import'" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html:15 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm import" +msgstr "تأكيد الحذف" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_errors.html:20 +#, fuzzy +#| msgid "Enter phone number" +msgid "Line number" +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:24 +msgid "Skipped" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:6 +msgid "Some rows failed to validate" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:10 +msgid "" +"Please correct these errors in your data where possible, then reupload it " +"using the form above." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:40 +msgid "Row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:26 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:44 +#, fuzzy +#| msgid "Error" +msgid "Errors" +msgstr "خطأ" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:70 +msgid "Non field specific" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html:6 +msgid "This exporter will export the following fields: " +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html:8 +msgid "This importer will import the following fields: " +msgstr "" + +#. Translators: Model verbose name and instance representation, +#. suitable to be an item in a list. +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:99 +#, python-format +msgid "%(class_name)s %(instance)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:111 +#, python-format +msgid "" +"Deleting %(class_name)s %(instance)s would require deleting the following " +"protected related objects: %(related_objects)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:8 +msgid "" +"Choose a date from the list below to revert to a previous version of this " +"object." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:28 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:57 +#, fuzzy +#| msgid "Country" +msgid "entry" +msgid_plural "entries" +msgstr[0] "الدولة" +msgstr[1] "الدولة" +msgstr[2] "الدولة" +msgstr[3] "الدولة" +msgstr[4] "الدولة" +msgstr[5] "الدولة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:32 +msgid "This object doesn't have a change history." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_form.html:16 +msgid "" +"Press the 'Revert' button below to revert to this version of the object." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_form.html:20 +msgid "Press the 'Change History' button below to edit the history." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:19 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:55 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:10 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:26 +msgid "Date/time" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:27 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:63 +#, fuzzy +#| msgid "Created by" +msgid "Changed by" +msgstr "أنشأ بواسطة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:31 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:77 +#, fuzzy +#| msgid "Change Stage" +msgid "Change reason" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:73 +msgid "None" +msgstr "لا شيء" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:8 +msgid "Revert" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:14 +#, fuzzy +#| msgid "Change Stage" +msgid "Change History" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/unfold/forms.py:71 +#, fuzzy +#| msgid "Select country" +msgid "Select action to run" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/forms.py:129 +msgid "" +"Raw passwords are not stored, so there is no way to see this user’s " +"password, but you can change the password using this form." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/mixins/base_model_admin.py:57 +#: venv/lib/python3.13/site-packages/unfold/mixins/base_model_admin.py:78 +#, fuzzy +#| msgid "Select country" +msgid "Select value" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:22 +msgid "Run the selected action" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:23 +msgid "Run" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:43 +msgid "Click here to select the objects across all pages" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:44 +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:50 +msgid "Clear selection" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_list.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:9 +#, python-format +msgid "Models in the %(name)s application" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_list.html:48 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list.html:99 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:59 +msgid "You don’t have permission to view or edit anything." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/add_form.html:6 +msgid "After you've created a user, you’ll be able to edit more user options." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/change_password.html:19 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/change_password.html:30 +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_form.html:29 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:30 +#, fuzzy +#| msgid "Password" +msgid "Change password" +msgstr "كلمة المرور" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_form_object_tools.html:6 +msgid "History" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_form_object_tools.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:77 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:33 +#, fuzzy +#| msgid "View on LinkedIn" +msgid "View on site" +msgstr "عرض على LinkedIn" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list.html:69 +msgid "Filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:32 +msgid "Select all rows" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:46 +#, fuzzy +#| msgid "Toggle user menu" +msgid "Toggle sorting" +msgstr "تبديل قائمة المستخدم" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:54 +msgid "Remove from sorting" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:60 +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:85 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/components/table.html:34 +msgid "Expand row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:16 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:32 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:48 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:55 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:49 +msgid "Objects" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:15 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:28 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:42 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(objects_name)s? All of the " +"following objects and their related items will be deleted:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/includes/object_delete_summary.html:5 +msgid "Summary" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:15 +msgid "Welcome back to" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:26 +#, python-format +msgid "" +"You are authenticated as %(username)s, but are not authorized to access this " +"page. Would you like to login to a different account?" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:47 +msgid "Log in" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:55 +msgid "Forgotten your password or username?" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:60 +msgid "" +"This object doesn’t have a change history. It probably wasn’t added via this " +"admin site." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/pagination.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +msgid "Show all" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/search_form.html:18 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command.html:24 +msgid "Type to search" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:28 +msgid "Save and continue editing" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:30 +msgid "Save and view" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:37 +msgid "Save and add another" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:43 +msgid "Save as new" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:14 +msgid "You have been successfully logged out from the administration" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:18 +msgid "Thanks for spending some quality time with the web site today." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:23 +msgid "Log in again" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_done.html:9 +msgid "Your password was changed." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_form.html:18 +msgid "" +"Please enter your old password, for security’s sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:17 +#, fuzzy +#| msgid "View Candidate" +msgid "View site" +msgstr "عرض المرشح" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:40 +#, fuzzy +#| msgid "Sign out" +msgid "Log out" +msgstr "تسجيل الخروج" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/actions_row.html:4 +#, fuzzy +#| msgid "Actions" +msgid "More actions" +msgstr "الإجراءات" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/add_link.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/add_link.html:8 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:4 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list.html:68 +#, fuzzy +#| msgid "Applications" +msgid "All applications" +msgstr "التقديمات" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:31 +msgid "Add" +msgstr "إضافة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "True" +msgstr "صحيح" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "False" +msgstr "خطأ" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:16 +msgid "Hide counts" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:20 +#, fuzzy +#| msgid "Your account" +msgid "Show counts" +msgstr "حسابك" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:27 +msgid "Clear all filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_history.html:7 +msgid "Recent searches" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_history.html:49 +msgid "No recent searches" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:44 +#, fuzzy +#| msgid "No templates match your search \"%(query)s\"." +msgid "No results matching your query" +msgstr "لا توجد قوالب تطابق بحثك \"%(query)s\"." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:57 +msgid "Loading more results..." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:62 +#, python-format +msgid "" +"\n" +" Found %(counter)s result in %(time)s seconds\n" +" " +msgid_plural "" +"\n" +" Found %(counter)s results in %(time)s seconds\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/delete_submit_line.html:5 +msgid "No, take me back" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/delete_submit_line.html:9 +msgid "Yes, I’m sure" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/display_header.html:10 +msgid "Record picture" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_heading.html:21 +#, fuzzy +#| msgid "Delete" +msgid "Delete?" +msgstr "حذف" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:12 +#, fuzzy +#| msgid "No meetings found." +msgid "No results found" +msgstr "لم يتم العثور على اجتماعات." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:16 +msgid "" +"This page yielded into no results. Create a new item or reset your filters." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:30 +msgid "Reset filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/header_back_button.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/header_back_button.html:15 +msgid "Go back" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/history.html:9 +#, fuzzy +#| msgid "Management Actions" +msgid "Recent actions" +msgstr "إجراءات الإدارة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/history.html:28 +msgid "Unknown content" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/messages/errornote.html:5 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:10 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:40 +#, fuzzy +#| msgid "Search templates by name..." +msgid "Search apps and models..." +msgstr "البحث عن القوالب بالاسم..." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:41 +#, fuzzy +#| msgid "Toggle navigation" +msgid "Filter navigation items" +msgstr "تبديل التنقل" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_branding.html:3 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_branding.html:6 +msgid "Django administration" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/tab_items.html:15 +msgid "General" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:16 +msgid "Dark" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:23 +msgid "Light" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:30 +msgid "System" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/unauthenticated_header.html:6 +msgid "Return to site" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input.html:6 +#, fuzzy +#| msgid "Interview" +msgid "Image preview" +msgstr "المقابلة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input.html:24 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input_small.html:17 +msgid "Choose file to upload" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:16 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:26 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:35 +#, python-format +msgid "View selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:45 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold_crispy/layout/table_inline_formset.html:65 +msgid "Add row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold.py:770 +msgid "Welcome" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold_list.py:118 +msgid "Select all objects on this page for an action" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/widgets.py:821 +#, fuzzy +#| msgid "Select country" +msgid "Select currency" +msgstr "اختر الدولة" + +#~ msgid "Password for accessing the portal (optional)" +#~ msgstr "كلمة المرور للوصول إلى البوابة (اختياري)" + +#, python-format +#~ msgid "" +#~ "Cannot transition from \"%(current)s\" to \"%(new)s\". Allowed " +#~ "transitions: %(allowed)s" +#~ msgstr "" +#~ "لا يمكن الانتقال من \"%(current)s\" إلى \"%(new)s\". الانتقالات المسموح " +#~ "بها: %(allowed)s" + +#~ msgid "Enter title" +#~ msgstr "أدخل العنوان" + +#~ msgid "Save Material" +#~ msgstr "حفظ المادة" + +#~ msgid "Help & Support" +#~ msgstr "المساعدة والدعم" + +#~ msgid "Application URL" +#~ msgstr "رابط التقديم" + +#~ msgid "Full URL where candidates will apply" +#~ msgstr "الرابط الكامل حيث سيقدم المرشحون" + +#~ msgid "Desired Start Date" +#~ msgstr "تاريخ البدء المطلوب" + +#~ msgid "Post Reach Field" +#~ msgstr "حقل وصول المنشور" + +#~ msgid "Hashtags" +#~ msgstr "الوسوم" + +#~ msgid "Start Date:" +#~ msgstr "تاريخ البدء:" + +#~ msgid "Info" +#~ msgstr "معلومات" + +#~ msgid "View All Existing Forms" +#~ msgstr "عرض جميع النماذج الموجودة" + +#~ msgid "Reports To:" +#~ msgstr "يقدم تقاريره إلى:" + +#~ msgid "Host Video" +#~ msgstr "فيديو المضيف" +>>>>>>> update1 #~ msgid "Are you sure?" #~ msgstr "هل أنت متأكد؟" +<<<<<<< HEAD #~ msgid "Cancel Transfer" #~ msgstr "إلغاء النقل" @@ -17181,3 +26190,31 @@ msgstr "تاريخ الإبطال" #~ msgid "show this help message and exit" #~ msgstr "إظهار رسالة المساعدة هذه والخروج" +======= +#~ msgid "Zoom API Response" +#~ msgstr "استجابة واجهة برمجة تطبيقات Zoom" + +#~ msgid "Start Time (ISO 8601):" +#~ msgstr "وقت البدء (ISO 8601):" + +#~ msgid "Duration (minutes):" +#~ msgstr "المدة (دقائق):" + +#~ msgid "Resume Document" +#~ msgstr "وثيقة السيرة الذاتية" + +#~ msgid "Parsed Data" +#~ msgstr "البيانات المحللة" + +#~ msgid "Activity" +#~ msgstr "النشاط" + +#~ msgid "Structured Resume Data" +#~ msgstr "بيانات السيرة الذاتية المنظمة" + +#~ msgid "" +#~ "Activity feed (e.g., stage changes, notes, interview history) will appear " +#~ "here." +#~ msgstr "" +#~ "سجل النشاط (مثلاً، تغييرات المرحلة، ملاحظات، سجل المقابلات) سيظهر هنا." +>>>>>>> update1 diff --git a/locale/ar/LC_MESSAGES/django.po.backup b/locale/ar/LC_MESSAGES/django.po.backup new file mode 100644 index 0000000..bcc2efe --- /dev/null +++ b/locale/ar/LC_MESSAGES/django.po.backup @@ -0,0 +1,9035 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-02 21:11+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#: recruitment/forms.py:240 recruitment/forms.py:1063 recruitment/models.py:424 +#: templates/recruitment/agency_portal_assignment_detail.html:539 +#: templates/recruitment/agency_portal_submit_candidate.html:187 +#: templates/user/profile.html:129 +msgid "First Name" +msgstr "الاسم الأول" + +#: recruitment/forms.py:241 recruitment/forms.py:1064 recruitment/models.py:425 +#: templates/recruitment/agency_portal_assignment_detail.html:545 +#: templates/recruitment/agency_portal_submit_candidate.html:198 +#: templates/user/profile.html:134 +msgid "Last Name" +msgstr "اسم العائلة" + +#: recruitment/forms.py:242 recruitment/models.py:427 +#: templates/jobs/job_candidates_list.html:228 +#: templates/jobs/job_candidates_list.html:340 +#: templates/participants/participants_list.html:214 +#: templates/recruitment/agency_confirm_delete.html:253 +#: templates/recruitment/agency_detail.html:289 +#: templates/recruitment/agency_list.html:187 +#: templates/recruitment/agency_portal_assignment_detail.html:560 +msgid "Phone" +msgstr "رقم الهاتف" + +#: recruitment/forms.py:243 recruitment/models.py:426 +#: recruitment/models.py:1660 recruitment/models.py:1734 +#: templates/jobs/job_candidates_list.html:227 +#: templates/meetings/meeting_details.html:305 +#: templates/participants/participants_list.html:213 +#: templates/recruitment/agency_confirm_delete.html:241 +#: templates/recruitment/agency_detail.html:266 +#: templates/recruitment/agency_detail.html:301 +#: templates/recruitment/agency_list.html:186 +#: templates/recruitment/agency_portal_assignment_detail.html:554 +#: templates/recruitment/candidate_detail.html:337 +#: templates/recruitment/candidate_list.html:270 +#: templates/recruitment/notification_list.html:50 +#: templates/user/admin_settings.html:175 +msgid "Email" +msgstr "البريد الإلكتروني" + +#: recruitment/forms.py:244 recruitment/forms.py:1067 recruitment/models.py:429 +msgid "Resume" +msgstr "السيرة الذاتية" + +#: recruitment/forms.py:245 +#, fuzzy +#| msgid "Hiring Agency" +msgid "Hiring Type" +msgstr "وكالة التوظيف" + +#: recruitment/forms.py:246 recruitment/models.py:180 recruitment/models.py:501 +#: recruitment/models.py:1287 +msgid "Hiring Agency" +msgstr "وكالة التوظيف" + +#: recruitment/forms.py:249 +#: templates/recruitment/agency_portal_submit_candidate.html:194 +msgid "Enter first name" +msgstr "أدخل الاسم الأول" + +#: recruitment/forms.py:250 +#: templates/recruitment/agency_portal_submit_candidate.html:205 +msgid "Enter last name" +msgstr "أدخل اسم العائلة" + +#: recruitment/forms.py:251 +#: templates/recruitment/agency_portal_submit_candidate.html:237 +msgid "Enter phone number" +msgstr "أدخل رقم الهاتف" + +#: recruitment/forms.py:252 +msgid "Enter email" +msgstr "أدخل البريد الإلكتروني" + +#: recruitment/forms.py:281 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:56 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_form.html:21 +msgid "Submit" +msgstr "إرسال" + +#: recruitment/forms.py:292 +msgid "New Application Stage" +msgstr "مرحلة تقديم جديدة" + +#: recruitment/forms.py:303 recruitment/models.py:713 +#: templates/includes/meeting_form.html:10 +#: templates/meetings/create_meeting.html:162 +#: templates/meetings/list_meetings.html:306 +#: templates/meetings/update_meeting.html:215 +#: templates/recruitment/candidate_interview_view.html:263 +msgid "Topic" +msgstr "الموضوع" + +#: recruitment/forms.py:304 recruitment/models.py:717 +#: recruitment/models.py:1564 recruitment/models.py:1583 +#: templates/interviews/schedule_interviews.html:169 +#: templates/interviews/schedule_interviews.html:201 +#: templates/meetings/create_meeting.html:166 +#: templates/meetings/list_meetings.html:310 +#: templates/meetings/reschedule_meeting.html:39 +#: templates/meetings/schedule_meeting_form.html:46 +#: templates/meetings/update_meeting.html:219 +#: templates/recruitment/schedule_meeting_form.html:55 +msgid "Start Time" +msgstr "وقت البدء" + +#: recruitment/forms.py:305 recruitment/models.py:719 +#: templates/meetings/list_meetings.html:266 +#: templates/meetings/list_meetings.html:311 +#: templates/meetings/meeting_details.html:265 +#: templates/recruitment/candidate_interview_view.html:264 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1868 +msgid "Duration" +msgstr "المدة" + +#: recruitment/forms.py:308 +msgid "Enter meeting topic" +msgstr "أدخل موضوع الاجتماع" + +#: recruitment/forms.py:310 +msgid "60" +msgstr "60" + +#: recruitment/forms.py:324 templates/meetings/create_meeting.html:180 +#: templates/meetings/list_meetings.html:179 +msgid "Create Meeting" +msgstr "إنشاء اجتماع" + +#: recruitment/forms.py:332 recruitment/models.py:688 +#: templates/recruitment/training_list.html:204 +#: templates/recruitment/training_update.html:144 +msgid "Title" +msgstr "العنوان" + +#: recruitment/forms.py:333 recruitment/models.py:689 recruitment/models.py:783 +#: templates/recruitment/training_update.html:158 +msgid "Content" +msgstr "المحتوى" + +#: recruitment/forms.py:334 recruitment/models.py:690 +#: templates/recruitment/training_update.html:150 +msgid "Video Link" +msgstr "رابط الفيديو" + +#: recruitment/forms.py:335 recruitment/models.py:692 +#: templates/recruitment/training_update.html:166 +#: venv/lib/python3.13/site-packages/django/db/models/fields/files.py:244 +msgid "File" +msgstr "ملف" + +#: recruitment/forms.py:338 +#, fuzzy +#| msgid "Enter material content" +msgid "Enter material title" +msgstr "أدخل محتوى المادة" + +#: recruitment/forms.py:339 +msgid "Enter material content" +msgstr "أدخل محتوى المادة" + +#: recruitment/forms.py:340 +msgid "https://www.youtube.com/watch?v=..." +msgstr "https://www.youtube.com/watch?v=..." + +#: recruitment/forms.py:359 +msgid "Create Material" +msgstr "إنشاء مادة" + +#: recruitment/forms.py:516 recruitment/models.py:422 +#: recruitment/models.py:1311 templates/forms/form_templates_list.html:270 +#: templates/meetings/list_meetings.html:256 +#: templates/meetings/list_meetings.html:308 +#: templates/meetings/reschedule_meeting.html:11 +#: templates/meetings/schedule_meeting_form.html:12 +#: templates/recruitment/agency_assignment_detail.html:129 +#: templates/recruitment/agency_assignment_list.html:113 +#: templates/recruitment/candidate_list.html:273 +#: templates/recruitment/schedule_meeting_form.html:18 +msgid "Job" +msgstr "الوظيفة" + +#: recruitment/forms.py:517 templates/forms/form_templates_list.html:269 +msgid "Template Name" +msgstr "اسم القالب" + +#: recruitment/forms.py:518 recruitment/models.py:1097 +#: templates/recruitment/agency_detail.html:377 +msgid "Description" +msgstr "الوصف" + +#: recruitment/forms.py:519 recruitment/models.py:1131 +#: recruitment/models.py:1296 templates/jobs/job_list.html:230 +#: templates/recruitment/agency_access_link_detail.html:31 +#: templates/recruitment/agency_access_link_form.html:89 +#: templates/recruitment/agency_assignment_list.html:87 +#: templates/recruitment/agency_detail.html:456 +#: templates/recruitment/agency_portal_dashboard.html:148 +#: templates/user/admin_settings.html:192 +msgid "Active" +msgstr "نشط" + +#: recruitment/forms.py:524 +msgid "Enter template name" +msgstr "أدخل اسم القالب" + +#: recruitment/forms.py:530 +msgid "Enter template description (optional)" +msgstr "أدخل وصف القالب (اختياري)" + +#: recruitment/forms.py:549 templates/forms/form_templates_list.html:384 +msgid "Create Template" +msgstr "إنشاء قالب" + +#: recruitment/forms.py:620 +msgid "Enter your comment or note" +msgstr "أدخل تعليقك أو ملاحظتك" + +#: recruitment/forms.py:625 templates/meetings/meeting_details.html:422 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:23 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:59 +msgid "Comment" +msgstr "تعليق" + +#: recruitment/forms.py:637 +msgid "Add Comment" +msgstr "إضافة تعليق" + +#: templates/recruitment/agency_portal_dashboard.html:76 +msgid "Total Assignments" +msgstr "إجمالي المهام" + +#: templates/recruitment/agency_portal_dashboard.html:87 +msgid "Active Assignments" +msgstr "المهام النشطة" + +#: templates/recruitment/agency_portal_dashboard.html:121 +msgid "Your Job Assignments" +msgstr "مهام الوظائف الخاصة بك" + +#: templates/recruitment/agency_portal_dashboard.html:166 +msgid "days left" +msgstr "أيام متبقية" + +#: templates/recruitment/agency_portal_dashboard.html:168 +msgid "days overdue" +msgstr "أيام متأخرة" + +#: templates/recruitment/agency_portal_dashboard.html:181 +msgid "Submission Progress" +msgstr "تقدم التقديم" + +#: templates/recruitment/agency_portal_dashboard.html:202 +msgid "Submissions Closed" +msgstr "التقديمات مغلقة" + +#: templates/recruitment/agency_portal_dashboard.html:230 +msgid "No Job Assignments Found" +msgstr "لم يتم العثور على مهام وظائف" + +#: templates/recruitment/agency_portal_dashboard.html:232 +msgid "You don't have any job assignments yet. Please contact the administrator if you expect to have assignments." +msgstr "ليس لديك أي مهام وظائف حتى الآن. يرجى التواصل مع المسؤول إذا كنت تتوقع وجود مهام." + +#: recruitment/forms.py:768 recruitment/models.py:1272 +#: templates/recruitment/agency_confirm_delete.html:218 +#: templates/recruitment/agency_list.html:184 +msgid "Agency Name" +msgstr "اسم الوكالة" + +#: recruitment/forms.py:769 recruitment/models.py:1274 +#: templates/recruitment/agency_confirm_delete.html:229 +#: templates/recruitment/agency_list.html:185 +msgid "Contact Person" +msgstr "شخص الاتصال" + +#: recruitment/forms.py:770 recruitment/forms.py:1065 +#: templates/participants/participants_detail.html:172 +#: templates/recruitment/agency_portal_submit_candidate.html:219 +#: templates/user/profile.html:139 +#, fuzzy +#| msgid "IP Address" +msgid "Email Address" +msgstr "عنوان IP" + +#: recruitment/forms.py:771 recruitment/forms.py:1066 recruitment/models.py:32 +#: recruitment/models.py:1735 templates/meetings/meeting_details.html:306 +#: templates/participants/participants_detail.html:178 +#: templates/recruitment/agency_portal_submit_candidate.html:230 +#, fuzzy +#| msgid "Position Number" +msgid "Phone Number" +msgstr "رقم المنصب" + +#: recruitment/forms.py:772 templates/recruitment/agency_detail.html:261 +#: templates/recruitment/agency_detail.html:313 +#: templates/recruitment/agency_list.html:189 +msgid "Website" +msgstr "الموقع الإلكتروني" + +#: recruitment/forms.py:773 templates/jobs/create_job.html:302 +#: templates/jobs/edit_job.html:302 +#: templates/recruitment/agency_detail.html:363 +#: templates/recruitment/agency_list.html:188 +msgid "Country" +msgstr "الدولة" + +#: recruitment/forms.py:774 recruitment/models.py:428 +#: templates/recruitment/agency_detail.html:339 +#: templates/recruitment/agency_portal_assignment_detail.html:568 +#, fuzzy +#| msgid "IP Address" +msgid "Address" +msgstr "عنوان IP" + +#: recruitment/forms.py:775 +#, fuzzy +#| msgid "Internal Job ID:" +msgid "Internal Notes" +msgstr "معرف الوظيفة الداخلي:" + +#: recruitment/forms.py:799 +#, fuzzy +#| msgid "Submitted by Agency" +msgid "Save Agency" +msgstr "مقدم من الوكالة" + +#: recruitment/forms.py:888 recruitment/models.py:1305 +#: templates/recruitment/agency_access_link_detail.html:46 +#: templates/recruitment/agency_assignment_detail.html:124 +#: templates/recruitment/agency_assignment_list.html:112 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency" +msgstr "اسم الوكالة" + +#: recruitment/forms.py:889 +#, fuzzy +#| msgid "Create New Job Posting" +msgid "Job Posting" +msgstr "إنشاء إعلان وظيفة جديد" + +#: recruitment/forms.py:890 recruitment/models.py:1316 +#: templates/recruitment/agency_portal_assignment_detail.html:174 +#, fuzzy +#| msgid "Candidates" +msgid "Maximum Candidates" +msgstr "المرشحون" + +#: recruitment/forms.py:891 recruitment/models.py:1328 +#, fuzzy +#| msgid "Deadline:" +msgid "Deadline Date" +msgstr "الموعد النهائي:" + +#: recruitment/forms.py:892 recruitment/forms.py:987 recruitment/models.py:1333 +#: recruitment/models.py:1501 +#, fuzzy +#| msgid "Active" +msgid "Is Active" +msgstr "نشط" + +#: recruitment/forms.py:893 recruitment/models.py:746 +#: recruitment/models.py:1338 recruitment/models.py:1687 +#: templates/includes/candidate_modal_body.html:70 +#: templates/includes/easy_logs.html:209 +#: templates/meetings/list_meetings.html:312 +#: templates/recruitment/agency_access_link_form.html:84 +#: templates/recruitment/agency_assignment_detail.html:136 +#: templates/recruitment/agency_assignment_list.html:84 +#: templates/recruitment/agency_assignment_list.html:116 +#: templates/recruitment/agency_portal_assignment_detail.html:148 +#: templates/recruitment/candidate_detail.html:557 +#: templates/recruitment/candidate_hired_view.html:236 +#: templates/recruitment/notification_detail.html:155 +#: templates/recruitment/notification_list.html:37 +#: templates/recruitment/partials/_candidate_table.html:12 +#: templates/user/admin_settings.html:176 +msgid "Status" +msgstr "الحالة" + +#: recruitment/forms.py:894 recruitment/models.py:1356 +#: templates/recruitment/agency_assignment_detail.html:160 +msgid "Admin Notes" +msgstr "ملاحظات المسؤول" + +#: recruitment/forms.py:928 +msgid "Save Assignment" +msgstr "حفظ التكليف" + +#: recruitment/forms.py:985 recruitment/models.py:1466 +#: templates/recruitment/agency_access_link_detail.html:37 +#: templates/recruitment/agency_access_link_form.html:37 +msgid "Assignment" +msgstr "التكليف" + +#: recruitment/forms.py:986 recruitment/models.py:1485 +#: templates/recruitment/agency_access_link_detail.html:56 +#: templates/recruitment/agency_access_link_form.html:52 +msgid "Expires At" +msgstr "ينتهي في" + +#: recruitment/forms.py:1011 +#: templates/recruitment/agency_access_link_form.html:4 +#: templates/recruitment/agency_access_link_form.html:12 +#: templates/recruitment/agency_access_link_form.html:130 +#, fuzzy +#| msgid "Create Meeting" +msgid "Create Access Link" +msgstr "إنشاء اجتماع" + +#: recruitment/forms.py:1091 +#: templates/recruitment/agency_portal_dashboard.html:198 +#: templates/recruitment/agency_portal_submit_candidate.html:4 +#: templates/recruitment/agency_portal_submit_candidate.html:104 +#: templates/recruitment/agency_portal_submit_candidate.html:337 +#: templates/recruitment/agency_portal_submit_candidate.html:561 +#, fuzzy +#| msgid "View Candidate" +msgid "Submit Candidate" +msgstr "عرض المرشح" + +#: recruitment/forms.py:1155 +#: templates/recruitment/agency_access_link_detail.html:98 +#: templates/recruitment/agency_assignment_detail.html:187 +#: templates/recruitment/agency_portal_login.html:145 +msgid "Access Token" +msgstr "رمز الوصول" + +#: recruitment/forms.py:1164 recruitment/models.py:729 +#: templates/account/login.html:164 templates/meetings/list_meetings.html:266 +#: templates/recruitment/agency_access_link_detail.html:109 +#: templates/recruitment/agency_assignment_detail.html:198 +#: templates/recruitment/agency_portal_login.html:167 +msgid "Password" +msgstr "كلمة المرور" + +#: recruitment/forms.py:1253 +#, fuzzy +#| msgid "Participant Video" +msgid "Select Participants" +msgstr "فيديو المشارك" + +#: recruitment/forms.py:1259 +#, fuzzy +#| msgid "Select country" +msgid "Select Users" +msgstr "اختر الدولة" + +#: recruitment/forms.py:1276 templates/includes/email_compose_form.html:20 +#: templates/recruitment/agency_portal_assignment_detail.html:488 +msgid "Subject" +msgstr "الموضوع" + +#: recruitment/forms.py:1287 templates/includes/email_compose_form.html:56 +#: templates/recruitment/agency_portal_assignment_detail.html:503 +#, fuzzy +#| msgid "Error Message" +msgid "Message" +msgstr "رسالة الخطأ" + +#: recruitment/forms.py:1295 templates/includes/email_compose_form.html:35 +msgid "Recipients" +msgstr "المستلمون" + +#: recruitment/forms.py:1303 +#, fuzzy +#| msgid "Candidate Information" +msgid "Include candidate information" +msgstr "معلومات المرشح" + +#: recruitment/forms.py:1312 +#, fuzzy +#| msgid "Meeting Details" +msgid "Include meeting details" +msgstr "تفاصيل الاجتماع" + +#: recruitment/forms.py:1376 +#, fuzzy +#| msgid "Please select a stage." +msgid "Please select at least one recipient." +msgstr "الرجاء اختيار مرحلة." + +#: recruitment/models.py:20 +msgid "Created at" +msgstr "تم الإنشاء في" + +#: recruitment/models.py:21 +msgid "Updated at" +msgstr "تم التحديث في" + +#: recruitment/models.py:23 +msgid "Slug" +msgstr "الرابط المختصر" + +#: recruitment/models.py:60 +#, fuzzy +#| msgid "Internal Information" +msgid "Internal Participant" +msgstr "المعلومات الداخلية" + +#: recruitment/models.py:61 +msgid "Internal staff involved in the recruitment process for this job" +msgstr "الموظفون الداخليون المشاركون في عملية التوظيف لهذه الوظيفة" + +#: recruitment/models.py:66 +msgid "External Participant" +msgstr "مشارك خارجي" + +#: recruitment/models.py:67 +msgid "External participants involved in the recruitment process for this job" +msgstr "المشاركون الخارجيون المشاركون في عملية التوظيف لهذه الوظيفة" + +#: recruitment/models.py:182 +msgid "External agency responsible for sourcing candidates for this role" +msgstr "وكالة خارجية مسؤولة عن توفير المرشحين لهذا المنصب" + +#: recruitment/models.py:187 +msgid "Reason for canceling the job posting" +msgstr "سبب إلغاء نشر الوظيفة" + +#: recruitment/models.py:188 +#, fuzzy +#| msgid "Cancelled" +msgid "Cancel Reason" +msgstr "ملغى" + +#: recruitment/models.py:193 +msgid "Name of person who cancelled this job" +msgstr "اسم الشخص الذي ألغى هذه الوظيفة" + +#: recruitment/models.py:194 +#, fuzzy +#| msgid "Cancelled" +msgid "Cancelled By" +msgstr "ملغى" + +#: recruitment/models.py:392 recruitment/models.py:437 +#: templates/jobs/job_candidates_list.html:211 +#: templates/recruitment/candidate_list.html:231 +msgid "Applied" +msgstr "تم التقديم" + +#: recruitment/models.py:393 templates/jobs/job_candidates_list.html:212 +#: templates/jobs/job_list.html:283 +#: templates/jobs/partials/applicant_tracking.html:128 +#: templates/recruitment/candidate_detail.html:410 +#: templates/recruitment/candidate_list.html:232 +msgid "Exam" +msgstr "الاختبار" + +#: recruitment/models.py:394 templates/jobs/job_candidates_list.html:213 +#: templates/jobs/job_list.html:284 +#: templates/jobs/partials/applicant_tracking.html:144 +#: templates/recruitment/candidate_detail.html:424 +#: templates/recruitment/candidate_list.html:233 +msgid "Interview" +msgstr "المقابلة" + +#: recruitment/models.py:395 templates/jobs/job_candidates_list.html:214 +#: templates/jobs/job_list.html:285 +#: templates/jobs/partials/applicant_tracking.html:160 +#: templates/recruitment/candidate_detail.html:439 +#: templates/recruitment/candidate_list.html:234 +#: templates/recruitment/candidate_offer_view.html:248 +msgid "Offer" +msgstr "العرض" + +#: recruitment/models.py:396 +#: templates/jobs/partials/applicant_tracking.html:176 +#: templates/recruitment/agency_detail.html:462 +#: templates/recruitment/candidate_hired_view.html:273 +msgid "Hired" +msgstr "تم التوظيف" + +#: recruitment/models.py:399 +#: templates/includes/candidate_update_exam_form.html:5 +#: templates/includes/candidate_update_interview_form.html:5 +msgid "Passed" +msgstr "نجح" + +#: recruitment/models.py:400 recruitment/models.py:1667 +#: templates/includes/candidate_update_exam_form.html:8 +#: templates/includes/candidate_update_interview_form.html:8 +#: templates/includes/easy_logs.html:267 +msgid "Failed" +msgstr "رسب" + +#: recruitment/models.py:403 +#: templates/includes/candidate_update_offer_form.html:5 +msgid "Accepted" +msgstr "قبل" + +#: recruitment/models.py:404 +#: templates/includes/candidate_update_offer_form.html:8 +#: templates/recruitment/agency_detail.html:468 +msgid "Rejected" +msgstr "رفض" + +#: recruitment/models.py:407 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant" +msgstr "المتقدمون" + +#: recruitment/models.py:408 recruitment/models.py:505 +#: templates/meetings/list_meetings.html:248 +#: templates/meetings/list_meetings.html:307 +msgid "Candidate" +msgstr "المرشح" + +#: recruitment/models.py:431 +#, fuzzy +#| msgid "Resume" +msgid "Resume Parsed" +msgstr "السيرة الذاتية" + +#: recruitment/models.py:434 +#, fuzzy +#| msgid "Create Candidate" +msgid "Potential Candidate" +msgstr "إنشاء مرشح" + +#: recruitment/models.py:436 +msgid "Parsed Summary" +msgstr "ملخص محلل" + +#: recruitment/models.py:442 templates/jobs/job_candidates_list.html:229 +#: templates/recruitment/agency_assignment_detail.html:241 +#: templates/recruitment/agency_portal_assignment_detail.html:243 +#: templates/recruitment/candidate_list.html:275 +#: templates/recruitment/partials/_candidate_table.html:13 +msgid "Stage" +msgstr "المرحلة" + +#: recruitment/models.py:450 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Status" +msgstr "المتقدمون" + +#: recruitment/models.py:452 templates/recruitment/candidate_exam_view.html:253 +msgid "Exam Date" +msgstr "تاريخ الاختبار" + +#: recruitment/models.py:458 +msgid "Exam Status" +msgstr "حالة الاختبار" + +#: recruitment/models.py:461 recruitment/models.py:1630 +msgid "Interview Date" +msgstr "تاريخ المقابلة" + +#: recruitment/models.py:468 +msgid "Interview Status" +msgstr "حالة المقابلة" + +#: recruitment/models.py:470 +msgid "Offer Date" +msgstr "تاريخ العرض" + +#: recruitment/models.py:476 +msgid "Offer Status" +msgstr "حالة العرض" + +#: recruitment/models.py:478 +#: templates/recruitment/candidate_hired_view.html:235 +#, fuzzy +#| msgid "Applied Date" +msgid "Hired Date" +msgstr "تاريخ التقديم" + +#: recruitment/models.py:479 +msgid "Join Date" +msgstr "تاريخ الانضمام" + +#: recruitment/models.py:491 templates/recruitment/candidate_list.html:276 +#, fuzzy +#| msgid "Hiring Agencies" +msgid "Hiring Source" +msgstr "وكالات التوظيف" + +#: recruitment/models.py:506 +#: templates/recruitment/agency_assignment_list.html:114 +#: templates/recruitment/agency_portal_dashboard.html:173 +msgid "Candidates" +msgstr "المرشحون" + +#: recruitment/models.py:695 +msgid "Created by" +msgstr "أنشأ بواسطة" + +#: recruitment/models.py:699 +msgid "Training Material" +msgstr "مادة تدريبية" + +#: recruitment/models.py:700 templates/recruitment/training_list.html:4 +#: templates/recruitment/training_list.html:128 +msgid "Training Materials" +msgstr "المواد التدريبية" + +#: recruitment/models.py:708 templates/meetings/list_meetings.html:203 +msgid "Waiting" +msgstr "في الانتظار" + +#: recruitment/models.py:709 templates/meetings/list_meetings.html:204 +msgid "Started" +msgstr "بدأ" + +#: recruitment/models.py:710 templates/meetings/list_meetings.html:205 +msgid "Ended" +msgstr "انتهى" + +#: recruitment/models.py:711 recruitment/models.py:1299 +#: recruitment/models.py:1637 +#: templates/recruitment/agency_assignment_list.html:90 +#: templates/recruitment/agency_portal_dashboard.html:152 +msgid "Cancelled" +msgstr "ملغى" + +#: recruitment/models.py:715 templates/meetings/meeting_details.html:266 +msgid "Meeting ID" +msgstr "معرف الاجتماع" + +#: recruitment/models.py:721 +msgid "Timezone" +msgstr "المنطقة الزمنية" + +#: recruitment/models.py:723 templates/meetings/meeting_details.html:274 +msgid "Join URL" +msgstr "رابط الانضمام" + +#: recruitment/models.py:726 +msgid "Participant Video" +msgstr "فيديو المشارك" + +#: recruitment/models.py:732 +msgid "Join Before Host" +msgstr "الانضمام قبل المضيف" + +#: recruitment/models.py:735 +msgid "Mute Upon Entry" +msgstr "كتم الصوت عند الدخول" + +#: recruitment/models.py:737 +msgid "Waiting Room" +msgstr "غرفة الانتظار" + +#: recruitment/models.py:740 +msgid "Zoom Gateway Response" +msgstr "استجابة بوابة زوم" + +#: recruitment/models.py:774 +#, fuzzy +#| msgid "Meetings" +msgid "Meeting" +msgstr "الاجتماعات" + +#: recruitment/models.py:780 +msgid "Author" +msgstr "المؤلف" + +#: recruitment/models.py:789 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Comment" +msgstr "تفاصيل الاجتماع" + +#: recruitment/models.py:790 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Comments" +msgstr "تفاصيل الاجتماع" + +#: recruitment/models.py:1089 +msgid "Source Name" +msgstr "اسم المصدر" + +#: recruitment/models.py:1090 recruitment/models.py:1093 +msgid "e.g., ATS, ERP " +msgstr "مثلاً، ATS, ERP" + +#: recruitment/models.py:1093 templates/meetings/meeting_details.html:307 +msgid "Source Type" +msgstr "نوع المصدر" + +#: recruitment/models.py:1098 +msgid "A description of the source" +msgstr "وصف المصدر" + +#: recruitment/models.py:1103 recruitment/models.py:1243 +#: templates/includes/easy_logs.html:210 +msgid "IP Address" +msgstr "عنوان IP" + +#: recruitment/models.py:1104 +msgid "The IP address of the source" +msgstr "عنوان IP الخاص بالمصدر" + +#: recruitment/models.py:1113 +msgid "API Key" +msgstr "مفتاح واجهة برمجة التطبيقات" + +#: recruitment/models.py:1114 +msgid "API key for authentication (will be encrypted)" +msgstr "مفتاح واجهة برمجة التطبيقات للمصادقة (سيتم تشفيره)" + +#: recruitment/models.py:1120 +msgid "API Secret" +msgstr "سر واجهة برمجة التطبيقات" + +#: recruitment/models.py:1121 +msgid "API secret for authentication (will be encrypted)" +msgstr "سر واجهة برمجة التطبيقات للمصادقة (سيتم تشفيره)" + +#: recruitment/models.py:1126 +msgid "Trusted IP Addresses" +msgstr "عناوين IP الموثوقة" + +#: recruitment/models.py:1127 +msgid "Comma-separated list of trusted IP addresses" +msgstr "قائمة عناوين IP الموثوقة مفصولة بفواصل" + +#: recruitment/models.py:1132 +msgid "Whether this source is active for integration" +msgstr "ما إذا كان هذا المصدر نشطاً للتكامل" + +#: recruitment/models.py:1137 +msgid "Integration Version" +msgstr "إصدار التكامل" + +#: recruitment/models.py:1138 +msgid "Version of the integration protocol" +msgstr "إصدار بروتوكول التكامل" + +#: recruitment/models.py:1143 +msgid "Last Sync At" +msgstr "آخر مزامنة في" + +#: recruitment/models.py:1144 +msgid "Timestamp of the last successful synchronization" +msgstr "الطابع الزمني لآخر مزامنة ناجحة" + +#: recruitment/models.py:1156 +msgid "Sync Status" +msgstr "حالة المزامنة" + +#: recruitment/models.py:1163 +#, fuzzy +#| msgid "Endpoint" +msgid "Sync Endpoint" +msgstr "نقطة النهاية" + +#: recruitment/models.py:1164 +msgid "Endpoint URL for sending candidate data (for outbound sync)" +msgstr "عنوان URL لنقطة النهاية لإرسال بيانات المرشح (للمزامنة الصادرة)" + +#: recruitment/models.py:1174 +#, fuzzy +#| msgid "HTTP Method" +msgid "Sync Method" +msgstr "طريقة HTTP" + +#: recruitment/models.py:1175 +msgid "HTTP method for outbound sync requests" +msgstr "طريقة HTTP لطلبات المزامنة الصادرة" + +#: recruitment/models.py:1185 +#, fuzzy +#| msgid "HTTP Method" +msgid "Test Method" +msgstr "طريقة HTTP" + +#: recruitment/models.py:1186 +msgid "HTTP method for connection testing" +msgstr "طريقة HTTP لاختبار الاتصال" + +#: recruitment/models.py:1191 +msgid "Custom Headers" +msgstr "رؤوس مخصصة" + +#: recruitment/models.py:1192 +msgid "JSON object with custom HTTP headers for sync requests" +msgstr "كائن JSON يحتوي على رؤوس HTTP مخصصة لطلبات المزامنة" + +#: recruitment/models.py:1196 +msgid "Supports Outbound Sync" +msgstr "يدعم المزامنة الصادرة" + +#: recruitment/models.py:1197 +msgid "Whether this source supports receiving candidate data from ATS" +msgstr "ما إذا كان هذا المصدر يدعم استقبال بيانات المرشح من نظام تتبع المتقدمين" + +#: recruitment/models.py:1204 recruitment/models.py:1226 +#: templates/jobs/job_list.html:269 +msgid "Source" +msgstr "المصدر" + +#: recruitment/models.py:1205 +msgid "Sources" +msgstr "المصادر" + +#: recruitment/models.py:1215 +msgid "Request" +msgstr "الطلب" + +#: recruitment/models.py:1216 +msgid "Response" +msgstr "الاستجابة" + +#: recruitment/models.py:1217 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:98 +msgid "Error" +msgstr "خطأ" + +#: recruitment/models.py:1218 +msgid "Sync" +msgstr "مزامنة" + +#: recruitment/models.py:1219 templates/jobs/job_list.html:395 +msgid "Create Job" +msgstr "إنشاء وظيفة" + +#: recruitment/models.py:1220 +msgid "Update Job" +msgstr "تحديث وظيفة" + +#: recruitment/models.py:1229 templates/includes/easy_logs.html:199 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:25 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:49 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:25 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:49 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:18 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:34 +msgid "Action" +msgstr "الإجراء" + +#: recruitment/models.py:1231 +msgid "Endpoint" +msgstr "نقطة النهاية" + +#: recruitment/models.py:1232 +msgid "HTTP Method" +msgstr "طريقة HTTP" + +#: recruitment/models.py:1234 +msgid "Request Data" +msgstr "بيانات الطلب" + +#: recruitment/models.py:1237 +msgid "Response Data" +msgstr "بيانات الاستجابة" + +#: recruitment/models.py:1240 +msgid "Status Code" +msgstr "رمز الحالة" + +#: recruitment/models.py:1242 +msgid "Error Message" +msgstr "رسالة الخطأ" + +#: recruitment/models.py:1245 +msgid "User Agent" +msgstr "وكيل المستخدم" + +#: recruitment/models.py:1248 +msgid "Processing Time (seconds)" +msgstr "وقت المعالجة (ثواني)" + +#: recruitment/models.py:1256 +msgid "Integration Log" +msgstr "سجل التكامل" + +#: recruitment/models.py:1257 +msgid "Integration Logs" +msgstr "سجلات التكامل" + +#: recruitment/models.py:1279 +msgid "Internal notes about the agency" +msgstr "ملاحظات داخلية حول الوكالة" + +#: recruitment/models.py:1280 +msgid "Select country" +msgstr "اختر الدولة" + +#: recruitment/models.py:1288 templates/recruitment/agency_list.html:4 +#: templates/recruitment/agency_list.html:131 +msgid "Hiring Agencies" +msgstr "وكالات التوظيف" + +#: recruitment/models.py:1297 recruitment/models.py:1638 +#: templates/recruitment/agency_assignment_list.html:89 +#: templates/recruitment/agency_portal_dashboard.html:150 +msgid "Completed" +msgstr "مكتمل" + +#: recruitment/models.py:1298 +#: templates/recruitment/agency_access_link_detail.html:60 +#: templates/recruitment/agency_assignment_detail.html:151 +#: templates/recruitment/agency_assignment_list.html:88 +#: templates/recruitment/agency_assignment_list.html:148 +#: templates/recruitment/agency_portal_assignment_detail.html:165 +#: templates/recruitment/agency_portal_dashboard.html:154 +msgid "Expired" +msgstr "منتهي الصلاحية" + +#: recruitment/models.py:1317 +msgid "Maximum candidates agency can submit for this job" +msgstr "الحد الأقصى للمرشحين الذين يمكن للوكالة تقديمهم لهذه الوظيفة" + +#: recruitment/models.py:1321 +#: templates/recruitment/agency_access_link_detail.html:71 +#, fuzzy +#| msgid "Candidates" +msgid "Candidates Submitted" +msgstr "المرشحون" + +#: recruitment/models.py:1322 +msgid "Number of candidates submitted so far" +msgstr "" + +#: recruitment/models.py:1326 +#: templates/recruitment/agency_portal_assignment_detail.html:410 +#, fuzzy +#| msgid "Applied Date" +msgid "Assigned Date" +msgstr "تاريخ التقديم" + +#: recruitment/models.py:1329 +msgid "Deadline for agency to submit candidates" +msgstr "" + +#: recruitment/models.py:1344 +#, fuzzy +#| msgid "Deadline:" +msgid "Deadline Extended" +msgstr "الموعد النهائي:" + +#: recruitment/models.py:1349 +#, fuzzy +#| msgid "Application Deadline" +msgid "Original Deadline" +msgstr "موعد نهائي للتقديم" + +#: recruitment/models.py:1350 +msgid "Original deadline before extensions" +msgstr "" + +#: recruitment/models.py:1357 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Internal notes about this assignment" +msgstr "ملاحظات داخلية حول الوكالة" + +#: recruitment/models.py:1361 +msgid "Agency Job Assignment" +msgstr "" + +#: recruitment/models.py:1362 +msgid "Agency Job Assignments" +msgstr "" + +#: recruitment/models.py:1401 +msgid "Deadline date must be in the future" +msgstr "" + +#: recruitment/models.py:1404 +msgid "Maximum candidates must be greater than 0" +msgstr "" + +#: recruitment/models.py:1407 +msgid "Candidates submitted cannot exceed maximum candidates" +msgstr "" + +#: recruitment/models.py:1474 +msgid "Unique Token" +msgstr "" + +#: recruitment/models.py:1478 +#, fuzzy +#| msgid "Password" +msgid "Access Password" +msgstr "كلمة المرور" + +#: recruitment/models.py:1479 +msgid "Password for agency access" +msgstr "" + +#: recruitment/models.py:1483 templates/participants/participants_list.html:217 +#: templates/recruitment/agency_access_link_detail.html:51 +#, fuzzy +#| msgid "Created at" +msgid "Created At" +msgstr "تم الإنشاء في" + +#: recruitment/models.py:1486 +msgid "When this access link expires" +msgstr "" + +#: recruitment/models.py:1491 +#: templates/recruitment/agency_access_link_detail.html:141 +msgid "Last Accessed" +msgstr "" + +#: recruitment/models.py:1497 +msgid "Access Count" +msgstr "" + +#: recruitment/models.py:1505 +msgid "Agency Access Link" +msgstr "" + +#: recruitment/models.py:1506 +msgid "Agency Access Links" +msgstr "" + +#: recruitment/models.py:1520 +msgid "Expiration date must be in the future" +msgstr "" + +#: recruitment/models.py:1565 recruitment/models.py:1584 +#: templates/interviews/schedule_interviews.html:176 +#: templates/interviews/schedule_interviews.html:205 +msgid "End Time" +msgstr "وقت الانتهاء" + +#: recruitment/models.py:1578 templates/interviews/schedule_interviews.html:146 +msgid "Start Date" +msgstr "تاريخ البدء" + +#: recruitment/models.py:1579 templates/interviews/schedule_interviews.html:153 +msgid "End Date" +msgstr "تاريخ الانتهاء" + +#: recruitment/models.py:1581 templates/interviews/schedule_interviews.html:160 +msgid "Working Days" +msgstr "أيام العمل" + +#: recruitment/models.py:1586 +msgid "Break Start Time" +msgstr "وقت بدء الاستراحة" + +#: recruitment/models.py:1587 +msgid "Break End Time" +msgstr "وقت انتهاء الاستراحة" + +#: recruitment/models.py:1590 +msgid "Interview Duration (minutes)" +msgstr "مدة المقابلة (دقائق)" + +#: recruitment/models.py:1593 +msgid "Buffer Time (minutes)" +msgstr "وقت المخزن المؤقت (دقائق)" + +#: recruitment/models.py:1631 +msgid "Interview Time" +msgstr "وقت المقابلة" + +#: recruitment/models.py:1635 +msgid "Scheduled" +msgstr "مجدول" + +#: recruitment/models.py:1636 +msgid "Confirmed" +msgstr "مؤكد" + +#: recruitment/models.py:1661 templates/recruitment/notification_list.html:49 +msgid "In-App" +msgstr "" + +#: recruitment/models.py:1664 +msgid "Pending" +msgstr "" + +#: recruitment/models.py:1665 templates/recruitment/notification_list.html:42 +msgid "Sent" +msgstr "" + +#: recruitment/models.py:1666 templates/recruitment/notification_list.html:41 +msgid "Read" +msgstr "" + +#: recruitment/models.py:1668 +msgid "Retrying" +msgstr "" + +#: recruitment/models.py:1674 +msgid "Recipient" +msgstr "" + +#: recruitment/models.py:1676 +msgid "Notification Message" +msgstr "" + +#: recruitment/models.py:1681 +msgid "Notification Type" +msgstr "" + +#: recruitment/models.py:1695 templates/recruitment/notification_detail.html:62 +#, fuzzy +#| msgid "Create Meeting" +msgid "Related Meeting" +msgstr "إنشاء اجتماع" + +#: recruitment/models.py:1698 +#, fuzzy +#| msgid "Scheduled" +msgid "Scheduled Send Time" +msgstr "مجدول" + +#: recruitment/models.py:1699 +msgid "The date and time this notification is scheduled to be sent." +msgstr "التاريخ والوقت المحدد لإرسال هذا الإشعار." + +#: recruitment/models.py:1703 +msgid "Send Attempts" +msgstr "محاولات الإرسال" + +#: recruitment/models.py:1704 +#, fuzzy +#| msgid "Error Message" +msgid "Last Error Message" +msgstr "رسالة الخطأ" + +#: recruitment/models.py:1708 +#, fuzzy +#| msgid "Location" +msgid "Notification" +msgstr "الموقع" + +#: recruitment/models.py:1709 templates/recruitment/notification_list.html:4 +#: templates/recruitment/notification_list.html:12 +#, fuzzy +#| msgid "Location" +msgid "Notifications" +msgstr "الموقع" + +#: recruitment/models.py:1733 +#, fuzzy +#| msgid "Participant Video" +msgid "Participant Name" +msgstr "فيديو المشارك" + +#: recruitment/models.py:1737 +#: templates/participants/participants_detail.html:184 +#: templates/participants/participants_list.html:216 +#, fuzzy +#| msgid "Description" +msgid "Designation" +msgstr "الوصف" + +#: recruitment/views.py:614 +msgid "Failed to start the job posting process. Please try again." +msgstr "فشل في بدء عملية نشر الوظيفة. يرجى المحاولة مرة أخرى." + +#: recruitment/views.py:2181 templates/includes/easy_logs.html:176 +#, fuzzy +#| msgid "User Agent" +msgid "User Authentication" +msgstr "وكيل المستخدم" + +#: recruitment/views.py:2184 templates/includes/easy_logs.html:182 +#, fuzzy +#| msgid "Request" +msgid "HTTP Requests" +msgstr "الطلب" + +#: recruitment/views.py:2187 templates/includes/easy_logs.html:170 +msgid "Model Changes (CRUD)" +msgstr "" + +#: recruitment/views_frontend.py:275 +msgid "You don't have permission to view this page." +msgstr "ليس لديك إذن لعرض هذه الصفحة." + +#: templates/account/account_inactive.html:7 +#: templates/account/account_inactive.html:131 +msgid "Account Inactive" +msgstr "الحساب غير نشط" + +#: templates/account/account_inactive.html:114 +#: templates/account/password_reset_done.html:136 +#: templates/account/password_reset_from_key.html:55 +#: templates/account/password_reset_from_key_done.html:52 +msgid "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية" +msgstr "" + +#: templates/account/account_inactive.html:115 +#: templates/account/password_reset_done.html:137 +#: templates/account/password_reset_from_key.html:56 +#: templates/account/password_reset_from_key_done.html:53 +msgid "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي" +msgstr "" + +#: templates/account/account_inactive.html:116 +#: templates/account/password_reset_done.html:138 +#: templates/account/password_reset_from_key.html:57 +#: templates/account/password_reset_from_key_done.html:54 +msgid "Princess Nourah bint Abdulrahman University" +msgstr "جامعة الأميرة نورة بنت عبدالرحمن" + +#: templates/account/account_inactive.html:117 +#: templates/account/password_reset_done.html:139 +#: templates/account/password_reset_from_key.html:58 +#: templates/account/password_reset_from_key_done.html:55 +#, fuzzy +#| msgid "King Abdullah Academic University Hospital" +msgid "King Abdullah bin Abdulaziz University Hospital" +msgstr "مستشفى الملك عبدالله الجامعي" + +#: templates/account/account_inactive.html:135 +msgid "" +"Access denied. This account has been marked as inactive by an administrator." +msgstr "" + +#: templates/account/account_inactive.html:138 +msgid "" +"If you believe this is an error, please contact the system administrator for " +"assistance." +msgstr "" + +#: templates/account/account_inactive.html:143 +#: templates/account/password_reset_from_key.html:109 +#, fuzzy +#| msgid "Re-post to LinkedIn" +msgid "Return to Sign In" +msgstr "إعادة النشر على LinkedIn" + +#: templates/account/email.html:6 templates/account/email.html:33 +#: templates/account/email.html:52 templates/account/logout.html:29 +#, fuzzy +#| msgid "IP Address" +msgid "Email Addresses" +msgstr "عنوان IP" + +#: templates/account/email.html:13 templates/account/logout.html:12 +#: templates/user/profile.html:111 +#, fuzzy +#| msgid "Settings" +msgid "Account Settings" +msgstr "الإعدادات" + +#: templates/account/email.html:14 templates/account/logout.html:13 +#: templates/user/profile.html:112 +msgid "Manage your personal details and security." +msgstr "إدارة تفاصيلك الشخصية والأمان." + +#: templates/account/email.html:28 templates/account/logout.html:26 +#: templates/recruitment/agency_portal_submit_candidate.html:182 +#: templates/user/profile.html:123 +#, fuzzy +#| msgid "Internal Information" +msgid "Personal Information" +msgstr "المعلومات الداخلية" + +#: templates/account/email.html:36 templates/account/logout.html:32 +#: templates/account/password_change.html:4 +#: templates/account/password_change.html:15 +#: templates/account/password_change.html:34 +#: templates/account/password_reset_from_key.html:90 +#: templates/user/admin_settings.html:223 templates/user/profile.html:161 +#: templates/user/staff_password_create.html:4 +#: templates/user/staff_password_create.html:16 +#: templates/user/staff_password_create.html:35 +#, fuzzy +#| msgid "Password" +msgid "Change Password" +msgstr "كلمة المرور" + +#: templates/account/email.html:40 templates/account/logout.html:5 +#: templates/account/logout.html:37 templates/account/logout.html:66 +#: templates/base.html:213 +msgid "Sign Out" +msgstr "تسجيل الخروج" + +#: templates/account/email.html:53 +msgid "" +"These email addresses are linked to your account. You can set the primary " +"address, resend verification, or remove an address." +msgstr "" + +#: templates/account/email.html:73 +msgid "Primary" +msgstr "أساسي" + +#: templates/account/email.html:76 +msgid "Verified" +msgstr "موثق" + +#: templates/account/email.html:78 +msgid "Unverified" +msgstr "غير موثق" + +#: templates/account/email.html:89 +msgid "Make Primary" +msgstr "جعل أساسي" + +#: templates/account/email.html:98 +#, fuzzy +#| msgid "Required Qualifications" +msgid "Re-send Verification" +msgstr "المؤهلات المطلوبة" + +#: templates/account/email.html:107 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_delete.html:4 +msgid "Remove" +msgstr "إزالة" + +#: templates/account/email.html:114 +#, fuzzy +#| msgid "No candidates found." +msgid "No email addresses found." +msgstr "لم يتم العثور على مرشحين." + +#: templates/account/email.html:121 +msgid "Add Email Address" +msgstr "إضافة عنوان بريد إلكتروني" + +#: templates/account/email.html:136 +#, fuzzy +#| msgid "Email" +msgid "Add Email" +msgstr "البريد الإلكتروني" + +#: templates/account/email/email_confirmation_message.html:5 +#: templates/account/email/email_confirmation_message.txt:4 +#: templates/account/email/password_reset_key_message.html:14 +#: templates/account/email/password_reset_key_message.txt:7 +msgid "Hello," +msgstr "مرحباً،" + +#: templates/account/email/email_confirmation_message.html:9 +#: templates/account/email/email_confirmation_message.txt:6 +msgid "" +"To verify the ownership of your email address, please click the confirmation " +"link below:" +msgstr "" + +#: templates/account/email/email_confirmation_message.html:15 +#: templates/account/email/email_confirmation_message.txt:9 +msgid "Confirm My KAAUH ATS Email" +msgstr "تأكيد بريدي الإلكتروني في نظام توظيف جامعة نورة" + +#: templates/account/email/email_confirmation_message.html:20 +#: templates/account/email/email_confirmation_message.txt:13 +msgid "" +"If you did not request this verification, you can safely ignore this email." +msgstr "" + +#: templates/account/email/email_confirmation_message.html:24 +#: templates/account/email/email_confirmation_message.txt:15 +msgid "Alternatively, copy and paste this link into your browser:" +msgstr "بدلاً من ذلك، انسخ والصق هذا الرابط في متصفحك:" + +#: templates/account/email/password_reset_key_message.html:10 +#: templates/account/email/password_reset_key_message.txt:5 +msgid "Password Reset Request" +msgstr "طلب إعادة تعيين كلمة المرور" + +#: templates/account/email/password_reset_key_message.html:16 +#: templates/account/email/password_reset_key_message.txt:9 +msgid "" +"You are receiving this email because you or someone else has requested a " +"password reset for your account at" +msgstr "" + +#: templates/account/email/password_reset_key_message.html:21 +#: templates/account/email/password_reset_key_message.txt:12 +msgid "Click Here to Reset Your Password" +msgstr "اضغط هنا لإعادة تعيين كلمة المرور" + +#: templates/account/email/password_reset_key_message.html:25 +#: templates/account/email/password_reset_key_message.txt:16 +msgid "This link is only valid for a limited time." +msgstr "هذا الرابط صالح لفترة محدودة فقط." + +#: templates/account/email/password_reset_key_message.html:27 +#: templates/account/email/password_reset_key_message.txt:18 +msgid "" +"If you did not request a password reset, please ignore this email. Your " +"password will remain unchanged." +msgstr "" + +#: templates/account/email/password_reset_key_message.html:30 +#: templates/account/email/password_reset_key_message.txt:20 +msgid "Thank you," +msgstr "شكراً لك،" + +#: templates/account/email/password_reset_key_message.html:31 +#: templates/account/email/password_reset_key_message.txt:21 +msgid "KAAUH ATS Team" +msgstr "فريق نظام توظيف جامعة نورة" + +#: templates/account/email/password_reset_key_message.html:36 +#: templates/account/email/password_reset_key_message.txt:24 +msgid "" +"If the button above does not work, copy and paste the following link into " +"your browser:" +msgstr "" + +#: templates/account/email_confirm.html:5 +msgid "Confirm Email Address" +msgstr "تأكيد عنوان البريد الإلكتروني" + +#: templates/account/email_confirm.html:13 +msgid "Account Verification" +msgstr "التحقق من الحساب" + +#: templates/account/email_confirm.html:14 +msgid "Verify your email to secure your account and unlock full features." +msgstr "تحقق من بريدك الإلكتروني لتأمين حسابك وإلغاء قفل جميع الميزات." + +#: templates/account/email_confirm.html:32 +msgid "Confirm Your Email Address" +msgstr "تأكيد عنوان بريدك الإلكتروني" + +#: templates/account/email_confirm.html:35 +#, python-format +msgid "" +"Please confirm that **%(email)s** is the correct email address for your " +"account." +msgstr "" + +#: templates/account/email_confirm.html:44 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm & Activate" +msgstr "تأكيد الحذف" + +#: templates/account/email_confirm.html:53 +msgid "Verification Failed" +msgstr "فشل التحقق" + +#: templates/account/email_confirm.html:56 +msgid "The email confirmation link is expired or invalid." +msgstr "رابط تأكيد البريد الإلكتروني منتهي الصلاحية أو غير صالح." + +#: templates/account/email_confirm.html:59 +msgid "" +"If you recently requested a link, please ensure you use the newest one. You " +"can request a new verification email from your account settings." +msgstr "" + +#: templates/account/email_confirm.html:63 +#, fuzzy +#| msgid "Settings" +msgid "Go to Settings" +msgstr "الإعدادات" + +#: templates/account/login.html:151 templates/account/login.html:178 +#, fuzzy +#| msgid "Sign out" +msgid "Sign In" +msgstr "تسجيل الخروج" + +#: templates/account/login.html:158 +#, fuzzy +#| msgid "Email" +msgid "Email *" +msgstr "البريد الإلكتروني" + +#: templates/account/login.html:159 +#, fuzzy +#| msgid "Enter email" +msgid "Enter your email" +msgstr "أدخل البريد الإلكتروني" + +#: templates/account/login.html:163 +#, fuzzy +#| msgid "Password" +msgid "Password *" +msgstr "كلمة المرور" + +#: templates/account/login.html:167 templates/account/password_reset.html:150 +#, fuzzy +#| msgid "Password" +msgid "Forgot Password?" +msgstr "كلمة المرور" + +#: templates/account/login.html:174 +msgid "Keep me signed in" +msgstr "ابق مسجلاً للدخول" + +#: templates/account/logout.html:50 +#, fuzzy +#| msgid "Sign Out" +msgid "Confirm Sign Out" +msgstr "تسجيل الخروج" + +#: templates/account/logout.html:52 +#, fuzzy +#| msgid "Are you sure you want to sign out?" +msgid "Are you sure you want to sign out of your account?" +msgstr "هل أنت متأكد من رغبتك في تسجيل الخروج؟" + +#: templates/account/logout.html:71 +#: templates/forms/form_templates_list.html:382 +#: templates/includes/email_compose_form.html:110 +#: templates/includes/meeting_form.html:40 +#: templates/interviews/schedule_interviews.html:221 +#: templates/jobs/create_job.html:327 templates/jobs/edit_job.html:327 +#: templates/jobs/job_detail.html:536 +#: templates/meetings/create_meeting.html:184 +#: templates/meetings/delete_meeting_form.html:8 +#: templates/meetings/meeting_details.html:399 +#: templates/meetings/set_candidate_form.html:6 +#: templates/meetings/update_meeting.html:237 +#: templates/participants/participants_detail.html:252 +#: templates/participants/participants_list.html:334 +#: templates/recruitment/agency_access_link_form.html:127 +#: templates/recruitment/agency_assignment_detail.html:430 +#: templates/recruitment/agency_assignment_form.html:213 +#: templates/recruitment/agency_confirm_delete.html:357 +#: templates/recruitment/agency_form.html:337 +#: templates/recruitment/agency_portal_assignment_detail.html:510 +#: templates/recruitment/agency_portal_assignment_detail.html:575 +#: templates/recruitment/agency_portal_assignment_detail.html:609 +#: templates/recruitment/agency_portal_submit_candidate.html:333 +#: templates/recruitment/notification_confirm_all_read.html:53 +#: templates/recruitment/notification_confirm_delete.html:33 +#: templates/recruitment/schedule_meeting_form.html:89 +msgid "Cancel" +msgstr "إلغاء" + +#: templates/account/password_change.html:19 +#: templates/user/staff_password_create.html:20 +msgid "" +"Please enter your current password and a new password to secure your account." +msgstr "" + +#: templates/account/password_change.html:41 +msgid "Return to Profile" +msgstr "العودة إلى الملف الشخصي" + +#: templates/account/password_reset.html:154 +msgid "Enter your e-mail address to reset your password." +msgstr "أدخل عنوان بريدك الإلكتروني لإعادة تعيين كلمة المرور." + +#: templates/account/password_reset.html:162 +#, fuzzy +#| msgid "IP Address" +msgid "E-mail Address" +msgstr "عنوان IP" + +#: templates/account/password_reset.html:179 +#, fuzzy +#| msgid "Password" +msgid "Reset My Password" +msgstr "كلمة المرور" + +#: templates/account/password_reset.html:185 +msgid "Remember your password?" +msgstr "تتذكر كلمة المرور؟" + +#: templates/account/password_reset.html:186 +msgid "Log In" +msgstr "تسجيل الدخول" + +#: templates/account/password_reset_done.html:7 +#: templates/account/password_reset_done.html:151 +msgid "Password Reset Sent" +msgstr "تم إرسال إعادة تعيين كلمة المرور" + +#: templates/account/password_reset_done.html:160 +msgid "" +"\n" +" We've **sent an email** to the address you provided " +"with instructions on how to reset your password.\n" +" " +msgstr "" + +#: templates/account/password_reset_done.html:168 +msgid "" +"Please check your inbox (and spam folder). The link in the email is " +"temporary and will expire soon for security reasons." +msgstr "" + +#: templates/account/password_reset_done.html:175 +msgid "Return to Login" +msgstr "العودة إلى تسجيل الدخول" + +#: templates/account/password_reset_from_key.html:7 +#: templates/account/password_reset_from_key.html:70 +#, fuzzy +#| msgid "Password" +msgid "Set New Password" +msgstr "كلمة المرور" + +#: templates/account/password_reset_from_key.html:76 +msgid "Please enter your new password below." +msgstr "يرجى إدخال كلمة المرور الجديدة أدناه." + +#: templates/account/password_reset_from_key.html:79 +msgid "You can then log in." +msgstr "" + +#: templates/account/password_reset_from_key.html:96 +msgid "Password Reset Failed" +msgstr "" + +#: templates/account/password_reset_from_key.html:98 +msgid "The password reset link is invalid or has expired." +msgstr "" + +#: templates/account/password_reset_from_key.html:102 +msgid "Request New Reset Link" +msgstr "" + +#: templates/account/password_reset_from_key_done.html:7 +#, fuzzy +#| msgid "Password" +msgid "Password Changed" +msgstr "كلمة المرور" + +#: templates/account/password_reset_from_key_done.html:69 +#, fuzzy +#| msgid "Posted successfully!" +msgid "Password Changed Successfully" +msgstr "تم النشر بنجاح!" + +#: templates/account/password_reset_from_key_done.html:72 +msgid "" +"Your password has been set. You can now use your new password to sign in." +msgstr "" + +#: templates/account/password_reset_from_key_done.html:77 +#: templates/account/verification_sent.html:182 +#, fuzzy +#| msgid "Post to LinkedIn" +msgid "Go to Sign In" +msgstr "النشر على LinkedIn" + +#: templates/account/verification_sent.html:153 +msgid "Verify Your Email Address" +msgstr "" + +#: templates/account/verification_sent.html:159 +msgid "" +"\n" +" We have sent an email to your email id for " +"verification. Follow the link provided to finalize the signup process.\n" +" " +msgstr "" + +#: templates/account/verification_sent.html:165 +msgid "" +"If you do not see the verification email in your main inbox, please check " +"your spam folder." +msgstr "" + +#: templates/account/verification_sent.html:169 +msgid "" +"Please contact us if you do not receive the verification email within a few " +"minutes." +msgstr "" + +#: templates/account/verification_sent.html:176 +msgid "Change or Resend Email" +msgstr "" + +#: templates/admin/sync_dashboard.html:4 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_index.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/base_site.html:3 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/index.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:15 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/layouts/base.html:7 +msgid "Django site admin" +msgstr "" + +#: templates/agency_base.html:9 +#, fuzzy +#| msgid "King Abdullah Academic University Hospital" +msgid "King Abdullah Academic University Hospital - Agency Portal" +msgstr "مستشفى الملك عبدالله الجامعي" + +#: templates/agency_base.html:10 +msgid "KAAUH Agency Portal" +msgstr "" + +#: templates/agency_base.html:31 templates/base.html:38 +msgid "Saudi Vision 2030" +msgstr "رؤية السعودية 2030" + +#: templates/agency_base.html:59 templates/base.html:59 templates/base.html:63 +msgid "kaauh logo green bg" +msgstr "" + +#: templates/agency_base.html:60 templates/agency_base.html:137 +#: templates/recruitment/agency_portal_login.html:126 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency Portal" +msgstr "اسم الوكالة" + +#: templates/agency_base.html:64 templates/base.html:68 +msgid "Toggle navigation" +msgstr "تبديل التنقل" + +#: templates/agency_base.html:75 templates/base.html:77 +#: templates/forms/partials/candidate_facing_base.html:267 +msgid "Toggle language menu" +msgstr "تبديل قائمة اللغة" + +#: templates/agency_base.html:103 templates/includes/easy_logs.html:261 +msgid "Logout" +msgstr "تسجيل الخروج" + +#: templates/agency_base.html:119 templates/base.html:302 +#: templates/jobs/job_detail.html:520 +#: templates/recruitment/candidate_exam_view.html:355 +#: templates/recruitment/candidate_hired_view.html:344 +#: templates/recruitment/candidate_interview_view.html:508 +#: templates/recruitment/candidate_screening_view.html:495 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:19 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:52 +msgid "Close" +msgstr "إغلاق" + +#: templates/agency_base.html:133 templates/base.html:315 +msgid "King Abdullah Academic University Hospital (KAAUH)." +msgstr "مستشفى الملك عبدالله الجامعي (KAAUH)." + +#: templates/agency_base.html:134 templates/base.html:316 +msgid "All rights reserved." +msgstr "جميع الحقوق محفوظة." + +#: templates/agency_base.html:175 +#, fuzzy +#| msgid "Are you sure you want to sign out?" +msgid "Are you sure you want to logout?" +msgstr "هل أنت متأكد من رغبتك في تسجيل الخروج؟" + +#: templates/base.html:9 +msgid "King Abdullah Academic University Hospital - Applicant Tracking System" +msgstr "مستشفى الملك عبدالله الجامعي - نظام تتبع المتقدمين" + +#: templates/base.html:10 +msgid "University ATS" +msgstr "نظام تتبع المتقدمين الجامعي" + +#: templates/base.html:134 +msgid "Toggle user menu" +msgstr "تبديل قائمة المستخدم" + +#: templates/base.html:141 templates/base.html:143 templates/base.html:159 +msgid "Your account" +msgstr "حسابك" + +#: templates/base.html:175 +msgid "My Profile" +msgstr "ملفي الشخصي" + +#: templates/base.html:179 +msgid "Settings" +msgstr "الإعدادات" + +#: templates/base.html:180 +msgid "Activity Log" +msgstr "سجل النشاط" + +#: templates/base.html:192 +msgid "Connect LinkedIn" +msgstr "ربط LinkedIn" + +#: templates/base.html:198 +msgid "LinkedIn Connected" +msgstr "LinkedIn متصل" + +#: templates/base.html:210 +msgid "Sign out" +msgstr "تسجيل الخروج" + +#: templates/base.html:232 templates/jobs/job_candidates_list.html:123 +#: templates/recruitment/partials/ai_overview_breadcromb.html:49 +msgid "Jobs" +msgstr "الوظائف" + +#: templates/base.html:240 templates/jobs/job_candidates_list.html:125 +#: templates/jobs/job_candidates_list.html:207 +#: templates/jobs/job_detail.html:291 templates/jobs/job_list.html:354 +#: templates/recruitment/partials/ai_overview_breadcromb.html:71 +msgid "Applicants" +msgstr "المتقدمون" + +#: templates/base.html:248 +#, fuzzy +#| msgid "Hiring Agencies" +msgid "Agencies" +msgstr "وكالات التوظيف" + +#: templates/base.html:258 +msgid "Meetings" +msgstr "الاجتماعات" + +#: templates/base.html:270 +#: templates/recruitment/candidate_interview_view.html:475 +#: templates/recruitment/candidate_interview_view.html:495 +#, fuzzy +#| msgid "Participant Video" +msgid "Participants" +msgstr "فيديو المشارك" + +#: templates/base.html:320 +#, fuzzy +#| msgid "Created by" +msgid "Powered by" +msgstr "أنشأ بواسطة" + +#: templates/base.html:361 +msgid "Are you sure you want to sign out?" +msgstr "هل أنت متأكد من رغبتك في تسجيل الخروج؟" + +#: templates/forms/application_detail.html:7 +#: templates/forms/application_detail.html:61 +msgid "Job Overview" +msgstr "نظرة عامة على الوظيفة" + +#: templates/forms/application_detail.html:39 +msgid "Ready to Apply?" +msgstr "" + +#: templates/forms/application_detail.html:42 +msgid "Review the job details, then apply below." +msgstr "" + +#: templates/forms/application_detail.html:46 +#: templates/forms/application_detail.html:106 +msgid "Apply for this Position" +msgstr "" + +#: templates/forms/application_detail.html:66 +#: templates/jobs/job_detail.html:240 +msgid "Salary:" +msgstr "الراتب:" + +#: templates/forms/application_detail.html:73 +#: templates/jobs/job_detail.html:177 +#: templates/recruitment/agency_portal_submit_candidate.html:142 +msgid "Deadline:" +msgstr "الموعد النهائي:" + +#: templates/forms/application_detail.html:77 +msgid "EXPIRED" +msgstr "منتهي الصلاحية" + +#: templates/forms/application_detail.html:80 +msgid "Not specified" +msgstr "" + +#: templates/forms/application_detail.html:84 +#: templates/jobs/job_candidates_list.html:149 +#: templates/jobs/job_detail.html:231 +msgid "Job Type:" +msgstr "نوع الوظيفة:" + +#: templates/forms/application_detail.html:85 +#: templates/jobs/job_candidates_list.html:146 +#: templates/jobs/job_detail.html:237 +msgid "Location:" +msgstr "الموقع:" + +#: templates/forms/application_detail.html:86 +#: templates/jobs/job_candidates_list.html:143 +#: templates/jobs/job_detail.html:225 +#: templates/recruitment/agency_portal_submit_candidate.html:139 +msgid "Department:" +msgstr "القسم:" + +#: templates/forms/application_detail.html:87 +msgid "JOB ID:" +msgstr "" + +#: templates/forms/application_detail.html:88 +#: templates/jobs/job_candidates_list.html:152 +#: templates/jobs/job_detail.html:234 +msgid "Workplace:" +msgstr "مكان العمل:" + +#: templates/forms/application_detail.html:91 +#: templates/jobs/create_job.html:189 templates/jobs/edit_job.html:189 +#: templates/jobs/job_detail.html:256 +msgid "Job Description" +msgstr "وصف الوظيفة" + +#: templates/forms/application_detail.html:92 +#, fuzzy +#| msgid "Required Qualifications" +msgid "Qualifications" +msgstr "المؤهلات المطلوبة" + +#: templates/forms/application_detail.html:93 +#: templates/jobs/create_job.html:221 templates/jobs/edit_job.html:221 +#: templates/jobs/job_detail.html:268 +msgid "Benefits" +msgstr "المزايا" + +#: templates/forms/application_detail.html:94 +#: templates/jobs/create_job.html:229 templates/jobs/edit_job.html:229 +#: templates/jobs/job_detail.html:274 +msgid "Application Instructions" +msgstr "تعليمات التقديم" + +#: templates/forms/application_submit_form.html:489 +#: templates/forms/partials/candidate_facing_base.html:11 +msgid "Application Form" +msgstr "نموذج التقديم" + +#: templates/forms/application_submit_form.html:504 +msgid "Review Your Application" +msgstr "مراجعة تقديمك" + +#: templates/forms/application_submit_form.html:516 +msgid "Back" +msgstr "العودة" + +#: templates/forms/application_submit_form.html:520 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/pagination_infinite.html:9 +msgid "Next" +msgstr "التالي" + +#: templates/forms/application_submit_form.html:529 +msgid "Submit Application" +msgstr "إرسال التقديم" + +#: templates/forms/form_submission_details.html:160 +#, fuzzy +#| msgid "Job Details" +msgid "Submission Details" +msgstr "تفاصيل الوظيفة" + +#: templates/forms/form_submission_details.html:162 +#: templates/forms/form_template_all_submissions.html:249 +#: templates/forms/form_template_all_submissions.html:364 +#, fuzzy +#| msgid "Back to Jobs" +msgid "Back to Submissions" +msgstr "العودة إلى الوظائف" + +#: templates/forms/form_submission_details.html:170 +msgid "Submission Metadata" +msgstr "" + +#: templates/forms/form_submission_details.html:176 +msgid "Submission ID:" +msgstr "" + +#: templates/forms/form_submission_details.html:180 +#: templates/recruitment/agency_portal_submit_candidate.html:151 +#, fuzzy +#| msgid "Submit" +msgid "Submitted:" +msgstr "إرسال" + +#: templates/forms/form_submission_details.html:184 +msgid "Form:" +msgstr "" + +#: templates/forms/form_submission_details.html:192 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Name:" +msgstr "المتقدمون" + +#: templates/forms/form_submission_details.html:198 +#, fuzzy +#| msgid "Email" +msgid "Email:" +msgstr "البريد الإلكتروني" + +#: templates/forms/form_submission_details.html:208 +#, fuzzy +#| msgid "Response" +msgid "Form Responses" +msgstr "الاستجابة" + +#: templates/forms/form_submission_details.html:219 +msgid "Field Property" +msgstr "" + +#: templates/forms/form_submission_details.html:227 +#, fuzzy +#| msgid "Response Data" +msgid "Response Value" +msgstr "بيانات الاستجابة" + +#: templates/forms/form_submission_details.html:233 +#, fuzzy +#| msgid "Download Resume" +msgid "Download File" +msgstr "تحميل السيرة الذاتية" + +#: templates/forms/form_submission_details.html:234 +#, fuzzy +#| msgid "Download Resume" +msgid "Download" +msgstr "تحميل السيرة الذاتية" + +#: templates/forms/form_submission_details.html:250 +#, fuzzy +#| msgid "No description provided" +msgid "Not provided" +msgstr "لم يتم تقديم وصف" + +#: templates/forms/form_submission_details.html:256 +#, fuzzy +#| msgid "Application Stage" +msgid "Associated Stage" +msgstr "مرحلة التقديم" + +#: templates/forms/form_submission_details.html:264 +msgid "Field Required" +msgstr "" + +#: templates/forms/form_submission_details.html:268 +#: templates/recruitment/candidate_detail.html:538 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:867 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:88 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:96 +msgid "Yes" +msgstr "نعم" + +#: templates/forms/form_submission_details.html:270 +#: templates/recruitment/candidate_detail.html:540 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:868 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:89 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:97 +msgid "No" +msgstr "لا" + +#: templates/forms/form_submission_details.html:281 +msgid "No response fields were found for this submission." +msgstr "" + +#: templates/forms/form_submission_details.html:282 +msgid "" +"This may occur if the form template was modified or responses were cleared." +msgstr "" + +#: templates/forms/form_template_all_submissions.html:232 +#: templates/forms/form_template_submissions_list.html:188 +#: templates/jobs/job_candidates_list.html:122 +#: templates/recruitment/agency_portal_submit_candidate.html:95 +msgid "Dashboard" +msgstr "لوحة التحكم" + +#: templates/forms/form_template_all_submissions.html:233 +#: templates/forms/form_template_submissions_list.html:189 +#: templates/forms/form_templates_list.html:152 +msgid "Form Templates" +msgstr "قوالب النماذج" + +#: templates/forms/form_template_all_submissions.html:234 +#: templates/forms/form_template_submissions_list.html:193 +#: templates/forms/form_templates_list.html:240 +#: templates/forms/form_templates_list.html:295 +#: templates/jobs/job_list.html:320 templates/jobs/job_list.html:373 +#: templates/recruitment/agency_access_link_detail.html:151 +msgid "Submissions" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:235 +msgid "All Submissions Table" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:244 +msgid "All Submissions for" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:258 +#: templates/forms/form_template_submissions_list.html:227 +msgid "Submission ID" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:259 +#: templates/forms/form_template_submissions_list.html:228 +#: templates/forms/form_template_submissions_list.html:265 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Name" +msgstr "المتقدمون" + +#: templates/forms/form_template_all_submissions.html:260 +#: templates/forms/form_template_submissions_list.html:229 +#: templates/forms/form_template_submissions_list.html:266 +#, fuzzy +#| msgid "Applicants" +msgid "Applicant Email" +msgstr "المتقدمون" + +#: templates/forms/form_template_all_submissions.html:261 +#: templates/forms/form_template_submissions_list.html:230 +#: templates/forms/form_template_submissions_list.html:267 +#, fuzzy +#| msgid "Submitted by Agency" +msgid "Submitted At" +msgstr "مقدم من الوكالة" + +#: templates/forms/form_template_all_submissions.html:315 +#: templates/forms/form_template_submissions_list.html:286 +#, python-format +msgid "" +"\n" +" Showing %(start)s to %(end)s of %(total)s " +"results.\n" +" " +msgstr "" + +#: templates/forms/form_template_all_submissions.html:336 +#: templates/forms/form_template_submissions_list.html:307 +msgid "Page" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:336 +#: templates/forms/form_template_submissions_list.html:307 +#: templates/includes/easy_logs.html:159 +msgid "of" +msgstr "" + +#: templates/forms/form_template_all_submissions.html:359 +#: templates/forms/form_template_submissions_list.html:330 +#, fuzzy +#| msgid "No meetings found." +msgid "No Submissions Found" +msgstr "لم يتم العثور على اجتماعات." + +#: templates/forms/form_template_all_submissions.html:361 +#: templates/forms/form_template_submissions_list.html:332 +msgid "There are no submissions for this form template yet." +msgstr "" + +#: templates/forms/form_template_submissions_list.html:202 +msgid "Submissions for" +msgstr "" + +#: templates/forms/form_template_submissions_list.html:208 +#, fuzzy +#| msgid "View All Applicants" +msgid "View All in Table" +msgstr "عرض جميع المتقدمين" + +#: templates/forms/form_template_submissions_list.html:211 +#: templates/forms/form_template_submissions_list.html:335 +#, fuzzy +#| msgid "Form Templates" +msgid "Back to Templates" +msgstr "قوالب النماذج" + +#: templates/forms/form_template_submissions_list.html:231 +#: templates/forms/form_templates_list.html:275 +#: templates/jobs/job_candidates_list.html:231 +#: templates/meetings/list_meetings.html:313 +#: templates/participants/participants_list.html:218 +#: templates/recruitment/agency_access_link_detail.html:168 +#: templates/recruitment/agency_assignment_detail.html:243 +#: templates/recruitment/agency_assignment_detail.html:339 +#: templates/recruitment/agency_assignment_list.html:117 +#: templates/recruitment/agency_list.html:191 +#: templates/recruitment/agency_portal_assignment_detail.html:245 +#: templates/recruitment/candidate_exam_view.html:255 +#: templates/recruitment/candidate_hired_view.html:237 +#: templates/recruitment/candidate_interview_view.html:269 +#: templates/recruitment/candidate_list.html:278 +#: templates/recruitment/candidate_offer_view.html:249 +#: templates/recruitment/candidate_screening_view.html:382 +#: templates/recruitment/notification_detail.html:126 +#: templates/recruitment/partials/_candidate_table.html:14 +#: templates/recruitment/training_list.html:207 +#: templates/user/admin_settings.html:179 +msgid "Actions" +msgstr "الإجراءات" + +#: templates/forms/form_template_submissions_list.html:243 +#: templates/forms/form_template_submissions_list.html:272 +#: templates/recruitment/agency_assignment_detail.html:268 +#: templates/recruitment/agency_assignment_list.html:160 +#: templates/recruitment/agency_portal_dashboard.html:209 +#, fuzzy +#| msgid "Core Details" +msgid "View Details" +msgstr "التفاصيل الأساسية" + +#: templates/forms/form_template_submissions_list.html:260 +#, fuzzy +#| msgid "Submit" +msgid "Submission" +msgstr "إرسال" + +#: templates/forms/form_templates_list.html:155 +#, fuzzy +#| msgid "Create Template" +msgid "Create New Template" +msgstr "إنشاء قالب" + +#: templates/forms/form_templates_list.html:165 +#, fuzzy +#| msgid "Template Name" +msgid "Search by Template Name" +msgstr "اسم القالب" + +#: templates/forms/form_templates_list.html:169 +msgid "Search templates by name..." +msgstr "البحث عن القوالب بالاسم..." + +#: templates/forms/form_templates_list.html:177 +#: templates/includes/search_form.html:16 +#: templates/recruitment/agency_assignment_list.html:77 +#: templates/recruitment/agency_assignment_list.html:98 +#: templates/recruitment/agency_list.html:167 +msgid "Search" +msgstr "بحث" + +#: templates/forms/form_templates_list.html:183 +#, fuzzy +#| msgid "Search" +msgid "Clear Search" +msgstr "بحث" + +#: templates/forms/form_templates_list.html:213 +#: templates/forms/form_templates_list.html:271 +msgid "Stages" +msgstr "المراحل" + +#: templates/forms/form_templates_list.html:217 +#: templates/forms/form_templates_list.html:272 +msgid "Fields" +msgstr "الحقول" + +#: templates/forms/form_templates_list.html:226 +msgid "No description provided" +msgstr "لم يتم تقديم وصف" + +#: templates/forms/form_templates_list.html:234 +#: templates/forms/form_templates_list.html:289 +#: templates/jobs/job_list.html:314 +msgid "Preview" +msgstr "معاينة" + +#: templates/forms/form_templates_list.html:237 +#: templates/forms/form_templates_list.html:292 +#: templates/jobs/job_candidates_list.html:257 +#: templates/jobs/job_candidates_list.html:353 templates/jobs/job_list.html:306 +#: templates/jobs/job_list.html:317 +#: templates/participants/participants_list.html:236 +#: templates/participants/participants_list.html:280 +#: templates/recruitment/agency_assignment_list.html:164 +#: templates/recruitment/agency_list.html:251 +#: templates/recruitment/agency_list.html:323 +#: templates/recruitment/candidate_list.html:329 +#: templates/recruitment/candidate_list.html:377 +#: templates/recruitment/training_list.html:181 +#: templates/recruitment/training_list.html:222 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:51 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:51 +msgid "Edit" +msgstr "تعديل" + +#: templates/forms/form_templates_list.html:243 +#: templates/forms/form_templates_list.html:298 +#: templates/jobs/job_candidates_list.html:260 +#: templates/jobs/job_candidates_list.html:356 +#: templates/meetings/list_meetings.html:284 +#: templates/meetings/list_meetings.html:372 +#: templates/participants/participants_detail.html:142 +#: templates/participants/participants_detail.html:255 +#: templates/participants/participants_list.html:239 +#: templates/participants/participants_list.html:282 +#: templates/participants/participants_list.html:337 +#: templates/recruitment/candidate_list.html:332 +#: templates/recruitment/candidate_list.html:379 +#: templates/recruitment/notification_detail.html:141 +#: templates/recruitment/training_list.html:183 +#: templates/recruitment/training_list.html:225 +#: templates/recruitment/training_update.html:184 +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:499 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:26 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:60 +msgid "Delete" +msgstr "حذف" + +#: templates/forms/form_templates_list.html:254 +#: templates/recruitment/agency_detail.html:518 +#: templates/recruitment/agency_form.html:393 +#: templates/recruitment/notification_confirm_delete.html:23 +msgid "Created:" +msgstr "تم الإنشاء:" + +#: templates/forms/form_templates_list.html:255 +msgid "ago" +msgstr "منذ" + +#: templates/forms/form_templates_list.html:273 +#: templates/recruitment/agency_confirm_delete.html:264 +#: templates/recruitment/agency_list.html:190 +#: templates/recruitment/agency_list.html:328 +#: templates/recruitment/notification_detail.html:169 +#: templates/recruitment/training_list.html:206 +msgid "Created" +msgstr "تم الإنشاء" + +#: templates/forms/form_templates_list.html:274 +#: templates/participants/participants_detail.html:218 +#, fuzzy +#| msgid "Last Updated:" +msgid "Last Updated" +msgstr "آخر تحديث:" + +#: templates/forms/form_templates_list.html:346 +msgid "No Form Templates Found" +msgstr "لم يتم العثور على قوالب نماذج" + +#: templates/forms/form_templates_list.html:349 +#, python-format +msgid "No templates match your search \"%(query)s\"." +msgstr "لا توجد قوالب تطابق بحثك \"%(query)s\"." + +#: templates/forms/form_templates_list.html:351 +msgid "You haven't created any form templates yet." +msgstr "لم تقم بإنشاء أي قوالب نماذج بعد." + +#: templates/forms/form_templates_list.html:355 +msgid "Create Your First Template" +msgstr "إنشاء أول قالب لك" + +#: templates/forms/form_templates_list.html:370 +#: templates/jobs/job_detail.html:347 +#, fuzzy +#| msgid "Create New Form" +msgid "Create New Form Template" +msgstr "إنشاء نموذج جديد" + +#: templates/forms/partials/candidate_facing_base.html:244 +#: templates/jobs/application_success.html:126 +msgid "KAAUH IMAGE" +msgstr "صورة KAAUH" + +#: templates/forms/partials/candidate_facing_base.html:261 +#: templates/jobs/application_success.html:141 +msgid "Careers" +msgstr "" + +#: templates/includes/candidate_exam_status_form.html:6 +#: templates/meetings/list_meetings.html:369 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:28 +msgid "Update" +msgstr "تحديث" + +#: templates/includes/candidate_modal_body.html:2 +#: templates/recruitment/candidate_exam_view.html:252 +#: templates/recruitment/candidate_screening_view.html:370 +#: templates/recruitment/partials/_candidate_table.html:11 +msgid "AI Score" +msgstr "" + +#: templates/includes/candidate_modal_body.html:8 +#, fuzzy +#| msgid "Job Title" +msgid "Job Fit" +msgstr "المسمى الوظيفي" + +#: templates/includes/candidate_modal_body.html:15 +#: templates/recruitment/candidate_detail.html:511 +msgid "Top Keywords" +msgstr "" + +#: templates/includes/candidate_modal_body.html:29 +msgid "Experience" +msgstr "" + +#: templates/includes/candidate_modal_body.html:31 +msgid "years" +msgstr "" + +#: templates/includes/candidate_modal_body.html:32 +msgid "Recent Role:" +msgstr "" + +#: templates/includes/candidate_modal_body.html:37 +msgid "Skills" +msgstr "" + +#: templates/includes/candidate_modal_body.html:39 +msgid "Soft Skills:" +msgstr "" + +#: templates/includes/candidate_modal_body.html:40 +msgid "Industry Match:" +msgstr "" + +#: templates/includes/candidate_modal_body.html:49 +#: templates/recruitment/candidate_detail.html:505 +msgid "Recommendation" +msgstr "" + +#: templates/includes/candidate_modal_body.html:54 +#: templates/recruitment/candidate_detail.html:496 +msgid "Strengths" +msgstr "" + +#: templates/includes/candidate_modal_body.html:59 +#: templates/recruitment/candidate_detail.html:499 +msgid "Weaknesses" +msgstr "" + +#: templates/includes/candidate_modal_body.html:64 +#: templates/recruitment/candidate_detail.html:551 +msgid "Criteria Assessment" +msgstr "" + +#: templates/includes/candidate_modal_body.html:69 +#: templates/recruitment/candidate_detail.html:556 +#, fuzzy +#| msgid "Create Material" +msgid "Criteria" +msgstr "إنشاء مادة" + +#: templates/includes/candidate_modal_body.html:79 +#: templates/includes/candidate_modal_body.html:100 +#: templates/recruitment/candidate_detail.html:566 +msgid "Met" +msgstr "" + +#: templates/includes/candidate_modal_body.html:81 +#: templates/includes/candidate_modal_body.html:102 +#: templates/recruitment/candidate_detail.html:568 +msgid "Not Met" +msgstr "" + +#: templates/includes/candidate_modal_body.html:97 +#, fuzzy +#| msgid "Description & Requirements" +msgid "Minimum Requirements" +msgstr "الوصف والمتطلبات" + +#: templates/includes/candidate_modal_body.html:108 +#: templates/recruitment/candidate_screening_view.html:275 +msgid "Screening Rating" +msgstr "" + +#: templates/includes/candidate_modal_body.html:116 +#: templates/recruitment/candidate_detail.html:583 +msgid "Language Fluency" +msgstr "" + +#: templates/includes/copy_to_clipboard.html:5 +#: templates/includes/easy_logs.html:269 +#: templates/recruitment/agency_assignment_detail.html:453 +msgid "Success" +msgstr "نجح" + +#: templates/includes/copy_to_clipboard.html:9 +#, python-format +msgid "Copied \"%(text)s\" to clipboard!" +msgstr "" + +#: templates/includes/easy_logs.html:5 +#, fuzzy +#| msgid "Dashboard" +msgid "Audit Dashboard" +msgstr "لوحة التحكم" + +#: templates/includes/easy_logs.html:153 +msgid "System Audit Logs" +msgstr "" + +#: templates/includes/easy_logs.html:157 +msgid "Viewing Logs" +msgstr "" + +#: templates/includes/easy_logs.html:159 +msgid "Displaying" +msgstr "" + +#: templates/includes/easy_logs.html:160 +msgid "total records." +msgstr "" + +#: templates/includes/easy_logs.html:197 templates/includes/easy_logs.html:206 +#: templates/includes/easy_logs.html:214 +#, fuzzy +#| msgid "Start Time" +msgid "Date/Time" +msgstr "وقت البدء" + +#: templates/includes/easy_logs.html:198 templates/includes/easy_logs.html:207 +#: templates/includes/easy_logs.html:215 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:17 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:33 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:15 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:33 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:30 +msgid "User" +msgstr "" + +#: templates/includes/easy_logs.html:200 +msgid "Model" +msgstr "" + +#: templates/includes/easy_logs.html:201 +msgid "Object PK" +msgstr "" + +#: templates/includes/easy_logs.html:202 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:35 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:81 +#, fuzzy +#| msgid "Change Stage" +msgid "Changes" +msgstr "تغيير المرحلة" + +#: templates/includes/easy_logs.html:208 +#: templates/recruitment/notification_detail.html:161 +#: templates/recruitment/notification_list.html:46 +#, fuzzy +#| msgid "Job Type" +msgid "Type" +msgstr "نوع الوظيفة" + +#: templates/includes/easy_logs.html:216 +#, fuzzy +#| msgid "HTTP Method" +msgid "Method" +msgstr "طريقة HTTP" + +#: templates/includes/easy_logs.html:217 +msgid "Path" +msgstr "" + +#: templates/includes/easy_logs.html:234 +msgid "CREATE" +msgstr "" + +#: templates/includes/easy_logs.html:235 +msgid "UPDATE" +msgstr "" + +#: templates/includes/easy_logs.html:236 +msgid "DELETE" +msgstr "" + +#: templates/includes/easy_logs.html:260 +msgid "Login" +msgstr "تسجيل الدخول" + +#: templates/includes/easy_logs.html:262 +#, fuzzy +#| msgid "Failed" +msgid "Failed Login" +msgstr "رسب" + +#: templates/includes/easy_logs.html:288 +msgid "No logs found for this section or the database is empty." +msgstr "" + +#: templates/includes/email_compose_form.html:10 +#: templates/recruitment/candidate_interview_view.html:523 +#, fuzzy +#| msgid "Host Email" +msgid "Compose Email" +msgstr "بريد المضيف الإلكتروني" + +#: templates/includes/email_compose_form.html:103 +msgid "Email will be sent to all selected recipients" +msgstr "" + +#: templates/includes/email_compose_form.html:116 +#: templates/includes/email_compose_form.html:236 +#: templates/includes/email_compose_form.html:267 +#: templates/recruitment/agency_detail.html:501 +#, fuzzy +#| msgid "Email" +msgid "Send Email" +msgstr "البريد الإلكتروني" + +#: templates/includes/email_compose_form.html:131 +#: templates/recruitment/agency_portal_submit_candidate.html:381 +#: templates/recruitment/candidate_detail.html:599 +msgid "Loading..." +msgstr "" + +#: templates/includes/email_compose_form.html:134 +msgid "Sending email..." +msgstr "" + +#: templates/includes/email_compose_form.html:208 +msgid "Sending..." +msgstr "" + +#: templates/includes/meeting_form.html:15 +#, fuzzy +#| msgid "Start Time" +msgid "Start Time and Date" +msgstr "وقت البدء" + +#: templates/includes/meeting_form.html:20 +#: templates/meetings/create_meeting.html:170 +#: templates/meetings/reschedule_meeting.html:51 +#: templates/meetings/schedule_meeting_form.html:62 +#: templates/meetings/update_meeting.html:223 +#: templates/recruitment/schedule_meeting_form.html:72 +msgid "Duration (minutes)" +msgstr "المدة (دقائق)" + +#: templates/includes/meeting_form.html:25 +msgid "Meeting Details (will appear after scheduling):" +msgstr "" + +#: templates/includes/meeting_form.html:26 +#, fuzzy +#| msgid "Join URL" +msgid "Join URL:" +msgstr "رابط الانضمام" + +#: templates/includes/meeting_form.html:27 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting ID:" +msgstr "معرف الاجتماع" + +#: templates/includes/meeting_form.html:32 +msgid "Click here to join meeting" +msgstr "" + +#: templates/includes/meeting_form.html:42 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Reschedule Meeting" +msgstr "حذف الاجتماع" + +#: templates/includes/meeting_form.html:42 +#: templates/includes/meeting_form.html:71 +#: templates/meetings/schedule_meeting_form.html:84 +#: templates/recruitment/schedule_meeting_form.html:4 +#: templates/recruitment/schedule_meeting_form.html:86 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Schedule Meeting" +msgstr "حذف الاجتماع" + +#: templates/includes/meeting_form.html:67 +#: templates/meetings/schedule_meeting_form.html:9 +#: templates/recruitment/candidate_interview_view.html:407 +#: templates/recruitment/schedule_meeting_form.html:15 +#, fuzzy +#| msgid "Interview" +msgid "Schedule Interview" +msgstr "المقابلة" + +#: templates/includes/meeting_form.html:83 +msgid "Processing..." +msgstr "" + +#: templates/includes/meeting_form.html:129 +msgid "An unknown error occurred." +msgstr "" + +#: templates/includes/meeting_form.html:137 +msgid "An error occurred while processing your request." +msgstr "" + +#: templates/includes/search_form.html:14 +msgid "Search..." +msgstr "بحث..." + +#: templates/interviews/schedule_interviews.html:110 +msgid "Bulk Interview Scheduling" +msgstr "" + +#: templates/interviews/schedule_interviews.html:113 +msgid "Configure time slots for:" +msgstr "" + +#: templates/interviews/schedule_interviews.html:117 +#: templates/recruitment/candidate_exam_view.html:187 +#: templates/recruitment/candidate_hired_view.html:211 +#: templates/recruitment/candidate_interview_view.html:190 +#: templates/recruitment/candidate_offer_view.html:189 +#: templates/recruitment/candidate_screening_view.html:238 +#, fuzzy +#| msgid "Back to Jobs" +msgid "Back to Job" +msgstr "العودة إلى الوظائف" + +#: templates/interviews/schedule_interviews.html:127 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Select Candidates" +msgstr "حذف المرشح" + +#: templates/interviews/schedule_interviews.html:131 +msgid "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)" +msgstr "" + +#: templates/interviews/schedule_interviews.html:141 +#, fuzzy +#| msgid "Schedule" +msgid "Schedule Details" +msgstr "الجدول" + +#: templates/interviews/schedule_interviews.html:183 +#, fuzzy +#| msgid "Duration (minutes)" +msgid "Duration (min)" +msgstr "المدة (دقائق)" + +#: templates/interviews/schedule_interviews.html:190 +#, fuzzy +#| msgid "Buffer Time (minutes)" +msgid "Buffer (min)" +msgstr "وقت المخزن المؤقت (دقائق)" + +#: templates/interviews/schedule_interviews.html:197 +#, fuzzy +#| msgid "Break End Time" +msgid "Daily Break Times" +msgstr "وقت انتهاء الاستراحة" + +#: templates/interviews/schedule_interviews.html:218 +#, fuzzy +#| msgid "Schedule" +msgid "Preview Schedule" +msgstr "الجدول" + +#: templates/jobs/application_success.html:7 +#, fuzzy +#| msgid "Application Stage" +msgid "Application Submitted - Thank You" +msgstr "مرحلة التقديم" + +#: templates/jobs/application_success.html:135 +#: templates/recruitment/dashboard.html:263 +msgid "Applications" +msgstr "التقديمات" + +#: templates/jobs/application_success.html:138 +msgid "Profile" +msgstr "الملف الشخصي" + +#: templates/jobs/application_success.html:150 +#, fuzzy +#| msgid "Application Information" +msgid "Application Confirmation" +msgstr "معلومات التقديم" + +#: templates/jobs/application_success.html:168 +msgid "Thank You!" +msgstr "" + +#: templates/jobs/application_success.html:169 +msgid "Your application has been submitted successfully" +msgstr "" + +#: templates/jobs/application_success.html:183 +msgid "" +"We appreciate your interest in joining our team. Our hiring team will review " +"your application and contact you if there's a potential match for this " +"position." +msgstr "" + +#: templates/jobs/application_success.html:188 +msgid "Return to Job Listings" +msgstr "" + +#: templates/jobs/career.html:224 templates/jobs/career.html:237 +msgid "Job ID#" +msgstr "" + +#: templates/jobs/career.html:225 templates/jobs/career.html:239 +#: templates/jobs/create_job.html:122 templates/jobs/edit_job.html:122 +#: templates/meetings/meeting_details.html:251 +#: templates/recruitment/agency_portal_assignment_detail.html:143 +msgid "Job Title" +msgstr "المسمى الوظيفي" + +#: templates/jobs/career.html:226 templates/jobs/career.html:241 +#, fuzzy +#| msgid "Hiring Agency" +msgid "Hiring" +msgstr "وكالة التوظيف" + +#: templates/jobs/career.html:227 templates/jobs/career.html:244 +#, fuzzy +#| msgid "Join Date" +msgid "Posting Date" +msgstr "تاريخ الانضمام" + +#: templates/jobs/career.html:228 templates/jobs/career.html:247 +#, fuzzy +#| msgid "Applied for:" +msgid "Apply Before" +msgstr "تقدم لـ:" + +#: templates/jobs/career.html:229 templates/jobs/career.html:249 +#: templates/recruitment/candidate_interview_view.html:266 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:27 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:98 +msgid "Link" +msgstr "ربط" + +#: templates/jobs/career.html:253 +#: templates/participants/participants_list.html:186 +#: templates/recruitment/candidate_list.html:243 +#, fuzzy +#| msgid "Applied" +msgid "Apply" +msgstr "تم التقديم" + +#: templates/jobs/create_job.html:105 templates/jobs/edit_job.html:105 +#, fuzzy +#| msgid "Create New Job Posting" +msgid "Edit Job Posting" +msgstr "إنشاء إعلان وظيفة جديد" + +#: templates/jobs/create_job.html:105 templates/jobs/edit_job.html:105 +msgid "Create New Job Posting" +msgstr "إنشاء إعلان وظيفة جديد" + +#: templates/jobs/create_job.html:116 templates/jobs/edit_job.html:116 +#, fuzzy +#| msgid "Core Details" +msgid "Core Position Details" +msgstr "التفاصيل الأساسية" + +#: templates/jobs/create_job.html:129 templates/jobs/edit_job.html:129 +#: templates/meetings/meeting_details.html:254 +msgid "Job Type" +msgstr "نوع الوظيفة" + +#: templates/jobs/create_job.html:137 templates/jobs/edit_job.html:137 +msgid "Workplace Type" +msgstr "نوع مكان العمل" + +#: templates/jobs/create_job.html:144 templates/jobs/edit_job.html:144 +msgid "Application Deadline" +msgstr "موعد نهائي للتقديم" + +#: templates/jobs/create_job.html:151 templates/jobs/edit_job.html:151 +msgid "Department" +msgstr "القسم" + +#: templates/jobs/create_job.html:158 templates/jobs/edit_job.html:158 +#: templates/recruitment/partials/stats_cards.html:37 +msgid "Open Positions" +msgstr "المناصب المفتوحة" + +#: templates/jobs/create_job.html:165 templates/jobs/edit_job.html:165 +#, fuzzy +#| msgid "Applications" +msgid "Max Applications" +msgstr "التقديمات" + +#: templates/jobs/create_job.html:183 templates/jobs/edit_job.html:183 +#, fuzzy +#| msgid "Content" +msgid "Job Content" +msgstr "المحتوى" + +#: templates/jobs/create_job.html:197 templates/jobs/edit_job.html:197 +msgid "Qualifications and Requirements" +msgstr "المؤهلات والمتطلبات" + +#: templates/jobs/create_job.html:211 templates/jobs/edit_job.html:211 +#, fuzzy +#| msgid "Application Instructions" +msgid "Benefits & Application Instructions" +msgstr "تعليمات التقديم" + +#: templates/jobs/create_job.html:243 templates/jobs/edit_job.html:243 +#, fuzzy +#| msgid "Internal Information" +msgid "Internal & Promotion" +msgstr "المعلومات الداخلية" + +#: templates/jobs/create_job.html:249 templates/jobs/edit_job.html:249 +msgid "Position Number" +msgstr "رقم المنصب" + +#: templates/jobs/create_job.html:256 templates/jobs/edit_job.html:256 +msgid "Reports To" +msgstr "يقدم تقاريره إلى" + +#: templates/jobs/create_job.html:266 templates/jobs/edit_job.html:266 +msgid "Hashtags (For Promotion/Search on Linkedin)" +msgstr "" + +#: templates/jobs/create_job.html:269 templates/jobs/edit_job.html:269 +#, fuzzy +#| msgid "Comma-separated list of trusted IP addresses" +msgid "Comma-separated list of hashtags, e.g., #hiring, #professor" +msgstr "قائمة عناوين IP الموثوقة مفصولة بفواصل" + +#: templates/jobs/create_job.html:282 templates/jobs/edit_job.html:282 +#, fuzzy +#| msgid "Location" +msgid "Location & Salary" +msgstr "الموقع" + +#: templates/jobs/create_job.html:288 templates/jobs/edit_job.html:288 +#: templates/recruitment/agency_detail.html:351 +msgid "City" +msgstr "المدينة" + +#: templates/jobs/create_job.html:295 templates/jobs/edit_job.html:295 +msgid "State/Province" +msgstr "الولاية/المقاطعة" + +#: templates/jobs/create_job.html:312 templates/jobs/edit_job.html:312 +msgid "Salary Range" +msgstr "نطاق الراتب" + +#: templates/jobs/create_job.html:330 templates/jobs/edit_job.html:330 +#, fuzzy +#| msgid "Create Job" +msgid "Save Job" +msgstr "إنشاء وظيفة" + +#: templates/jobs/job_candidates_list.html:118 +#, fuzzy +#| msgid "Applicants" +msgid "Applicants for" +msgstr "المتقدمون" + +#: templates/jobs/job_candidates_list.html:131 +#, fuzzy +#| msgid "New Application Stage" +msgid "Add New Applicant" +msgstr "مرحلة تقديم جديدة" + +#: templates/jobs/job_candidates_list.html:162 +#: templates/jobs/job_detail.html:315 +#, fuzzy +#| msgid "Applicants" +msgid "Total Applicants" +msgstr "المتقدمون" + +#: templates/jobs/job_candidates_list.html:175 +#, fuzzy +#| msgid "Applicants" +msgid "Search Applicants" +msgstr "المتقدمون" + +#: templates/jobs/job_candidates_list.html:179 +msgid "Search by name, email, phone, or stage..." +msgstr "" + +#: templates/jobs/job_candidates_list.html:185 +msgid "Filter Results" +msgstr "" + +#: templates/jobs/job_candidates_list.html:190 +#: templates/recruitment/notification_list.html:200 +msgid "Clear Filters" +msgstr "" + +#: templates/jobs/job_candidates_list.html:210 +#: templates/recruitment/candidate_list.html:230 +#, fuzzy +#| msgid "Stages" +msgid "All Stages" +msgstr "المراحل" + +#: templates/jobs/job_candidates_list.html:226 +#: templates/meetings/meeting_details.html:303 +#: templates/participants/participants_list.html:212 +#: templates/recruitment/agency_assignment_detail.html:239 +#: templates/recruitment/agency_portal_assignment_detail.html:241 +#: templates/recruitment/candidate_exam_view.html:250 +#: templates/recruitment/candidate_hired_view.html:232 +#: templates/recruitment/candidate_interview_view.html:261 +#: templates/recruitment/candidate_list.html:269 +#: templates/recruitment/candidate_offer_view.html:246 +#: templates/recruitment/candidate_screening_view.html:364 +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:19 +msgid "Name" +msgstr "الاسم" + +#: templates/jobs/job_candidates_list.html:230 +#: templates/jobs/job_candidates_list.html:343 +#: templates/recruitment/candidate_detail.html:355 +msgid "Applied Date" +msgstr "تاريخ التقديم" + +#: templates/jobs/job_candidates_list.html:253 templates/jobs/job_list.html:303 +#: templates/jobs/job_list.html:365 templates/meetings/list_meetings.html:272 +#: templates/meetings/list_meetings.html:366 +#: templates/participants/participants_list.html:232 +#: templates/participants/participants_list.html:276 +#: templates/recruitment/agency_list.html:246 +#: templates/recruitment/agency_list.html:319 +#: templates/recruitment/candidate_list.html:325 +#: templates/recruitment/candidate_list.html:373 +#: templates/recruitment/candidate_update.html:104 +#: templates/recruitment/training_list.html:177 +#: templates/recruitment/training_list.html:218 +#: templates/recruitment/training_update.html:119 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:65 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:38 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:25 +msgid "View" +msgstr "عرض" + +#: templates/jobs/job_candidates_list.html:277 +#, fuzzy +#| msgid "Rejected" +msgid "Selected" +msgstr "رفض" + +#: templates/jobs/job_candidates_list.html:280 +#, fuzzy +#| msgid "Interview" +msgid "Mark Interview" +msgstr "المقابلة" + +#: templates/jobs/job_candidates_list.html:284 +#, fuzzy +#| msgid "Offer" +msgid "Mark Offer" +msgstr "العرض" + +#: templates/jobs/job_candidates_list.html:349 +#, fuzzy +#| msgid "Profile" +msgid "View Profile" +msgstr "الملف الشخصي" + +#: templates/jobs/job_candidates_list.html:376 +#, fuzzy +#| msgid "No applicants yet" +msgid "No applicants found" +msgstr "لا يوجد متقدمون بعد" + +#: templates/jobs/job_candidates_list.html:377 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "There are no candidates who have applied for this position yet." +msgstr "سيظهر المرشحون هنا بمجرد تقديمهم لهذا المنصب." + +#: templates/jobs/job_candidates_list.html:379 +#, fuzzy +#| msgid "Applicants" +msgid "Add First Applicant" +msgstr "المتقدمون" + +#: templates/jobs/job_detail.html:171 +msgid "JOB ID: " +msgstr "" + +#: templates/jobs/job_detail.html:208 +msgid "Share Public Link" +msgstr "" + +#: templates/jobs/job_detail.html:212 +#: templates/meetings/meeting_details.html:270 +#: templates/meetings/meeting_details.html:475 +msgid "Copied!" +msgstr "" + +#: templates/jobs/job_detail.html:220 +msgid "Administrative & Location" +msgstr "الإداري والموقع" + +#: templates/jobs/job_detail.html:221 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit JOb" +msgstr "تعديل الوظيفة" + +#: templates/jobs/job_detail.html:228 +msgid "Position No:" +msgstr "رقم المنصب:" + +#: templates/jobs/job_detail.html:243 +msgid "Created By:" +msgstr "أنشأ بواسطة:" + +#: templates/jobs/job_detail.html:246 +#, fuzzy +#| msgid "Created at" +msgid "Created At:" +msgstr "تم الإنشاء في" + +#: templates/jobs/job_detail.html:249 +#, fuzzy +#| msgid "Updated at" +msgid "Updated At:" +msgstr "تم التحديث في" + +#: templates/jobs/job_detail.html:262 +msgid "Required Qualifications" +msgstr "المؤهلات المطلوبة" + +#: templates/jobs/job_detail.html:296 +msgid "Tracking" +msgstr "" + +#: templates/jobs/job_detail.html:301 +#, fuzzy +#| msgid "Form Templates" +msgid "Form Template" +msgstr "قوالب النماذج" + +#: templates/jobs/job_detail.html:306 +#, fuzzy +#| msgid "Connect LinkedIn" +msgid "LinkedIn" +msgstr "ربط LinkedIn" + +#: templates/jobs/job_detail.html:319 +#, fuzzy +#| msgid "Create Template" +msgid "Create Applicant" +msgstr "إنشاء قالب" + +#: templates/jobs/job_detail.html:322 +#, fuzzy +#| msgid "Applicants" +msgid "Manage Applicants" +msgstr "المتقدمون" + +#: templates/jobs/job_detail.html:329 +#, fuzzy +#| msgid "Application Stage" +msgid "Applicant Stages" +msgstr "مرحلة التقديم" + +#: templates/jobs/job_detail.html:332 +msgid "" +"The applicant tracking flow is defined by the attached Form Template. View " +"the Form Template tab to manage stages and fields." +msgstr "" + +#: templates/jobs/job_detail.html:339 +msgid "Form Management" +msgstr "إدارة النماذج" + +#: templates/jobs/job_detail.html:342 +msgid "Manage the custom application forms associated with this job posting." +msgstr "إدارة نماذج التقديم المخصصة المرتبطة بهذا الإعلان الوظيفي." + +#: templates/jobs/job_detail.html:352 +#, fuzzy +#| msgid "Form Templates" +msgid "View Form Template" +msgstr "قوالب النماذج" + +#: templates/jobs/job_detail.html:355 +msgid "" +"This job status is not active, the form will appear once the job is made " +"active" +msgstr "" + +#: templates/jobs/job_detail.html:368 +msgid "LinkedIn Integration" +msgstr "تكامل LinkedIn" + +#: templates/jobs/job_detail.html:372 +msgid "Posted successfully!" +msgstr "تم النشر بنجاح!" + +#: templates/jobs/job_detail.html:376 +msgid "View on LinkedIn" +msgstr "عرض على LinkedIn" + +#: templates/jobs/job_detail.html:380 +msgid "Posted on:" +msgstr "تم النشر في:" + +#: templates/jobs/job_detail.html:383 +msgid "This job has not been posted to LinkedIn yet." +msgstr "لم يتم نشر هذه الوظيفة على LinkedIn بعد." + +#: templates/jobs/job_detail.html:391 +msgid "Re-post to LinkedIn" +msgstr "إعادة النشر على LinkedIn" + +#: templates/jobs/job_detail.html:391 +msgid "Post to LinkedIn" +msgstr "النشر على LinkedIn" + +#: templates/jobs/job_detail.html:396 +msgid "Upload Image for Post" +msgstr "رفع صورة للمنشور" + +#: templates/jobs/job_detail.html:401 +msgid "You need to" +msgstr "تحتاج إلى" + +#: templates/jobs/job_detail.html:401 +msgid "authenticate with LinkedIn" +msgstr "المصادقة مع LinkedIn" + +#: templates/jobs/job_detail.html:401 +msgid "first." +msgstr "أولاً." + +#: templates/jobs/job_detail.html:408 +#: templates/recruitment/candidate_hired_view.html:466 +msgid "Error:" +msgstr "خطأ:" + +#: templates/jobs/job_detail.html:413 +#, fuzzy +#| msgid "LinkedIn Connected" +msgid "Update LinkedIn Content" +msgstr "LinkedIn متصل" + +#: templates/jobs/job_detail.html:426 +msgid "Candidate Categories & Scores" +msgstr "" + +#: templates/jobs/job_detail.html:441 +msgid "Key Performance Indicators" +msgstr "" + +#: templates/jobs/job_detail.html:454 +msgid "Avg. AI Score" +msgstr "" + +#: templates/jobs/job_detail.html:465 +#: templates/recruitment/partials/stats_cards.html:97 +msgid "High Potential" +msgstr "" + +#: templates/jobs/job_detail.html:476 +#, fuzzy +#| msgid "Interview" +msgid "Time to Interview" +msgstr "المقابلة" + +#: templates/jobs/job_detail.html:487 +msgid "Avg. Exam Review" +msgstr "" + +#: templates/jobs/job_detail.html:497 +msgid "Vacancy Fill Rate" +msgstr "" + +#: templates/jobs/job_detail.html:519 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit Job Status" +msgstr "تعديل الوظيفة" + +#: templates/jobs/job_detail.html:525 +#, fuzzy +#| msgid "Sync Status" +msgid "Select New Status" +msgstr "حالة المزامنة" + +#: templates/jobs/job_detail.html:531 +msgid "Status form not available. Please check your view." +msgstr "" + +#: templates/jobs/job_detail.html:537 +#: templates/meetings/meeting_details.html:396 +#: templates/recruitment/agency_portal_assignment_detail.html:578 +#: templates/user/profile.html:147 +msgid "Save Changes" +msgstr "" + +#: templates/jobs/job_list.html:204 +#, fuzzy +#| msgid "Create New Job Posting" +msgid "Job Postings" +msgstr "إنشاء إعلان وظيفة جديد" + +#: templates/jobs/job_list.html:207 +#, fuzzy +#| msgid "Create Job" +msgid "Create New Job" +msgstr "إنشاء وظيفة" + +#: templates/jobs/job_list.html:216 +msgid "Search by Title or Department" +msgstr "" + +#: templates/jobs/job_list.html:226 templates/meetings/list_meetings.html:200 +#, fuzzy +#| msgid "Offer Status" +msgid "Filter by Status" +msgstr "حالة العرض" + +#: templates/jobs/job_list.html:228 templates/meetings/list_meetings.html:202 +#: templates/recruitment/agency_assignment_list.html:86 +#, fuzzy +#| msgid "Status" +msgid "All Statuses" +msgstr "الحالة" + +#: templates/jobs/job_list.html:229 +#, fuzzy +#| msgid "Draft Jobs" +msgid "Draft" +msgstr "مسودات الوظائف" + +#: templates/jobs/job_list.html:231 +#, fuzzy +#| msgid "Close" +msgid "Closed" +msgstr "إغلاق" + +#: templates/jobs/job_list.html:232 +msgid "Archived" +msgstr "" + +#: templates/jobs/job_list.html:238 templates/meetings/list_meetings.html:215 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:7 +#, fuzzy +#| msgid "Applicants" +msgid "Apply Filters" +msgstr "المتقدمون" + +#: templates/jobs/job_list.html:242 templates/meetings/list_meetings.html:219 +#: templates/participants/participants_list.html:190 +#: templates/recruitment/candidate_list.html:247 +#: templates/recruitment/notification_list.html:60 +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:527 +msgid "Clear" +msgstr "مسح" + +#: templates/jobs/job_list.html:268 +#, fuzzy +#| msgid "Job Title" +msgid "Job Title / ID" +msgstr "المسمى الوظيفي" + +#: templates/jobs/job_list.html:270 +msgid "Max Apps" +msgstr "" + +#: templates/jobs/job_list.html:271 +#: templates/recruitment/agency_assignment_detail.html:144 +#: templates/recruitment/agency_assignment_list.html:115 +#: templates/recruitment/agency_portal_assignment_detail.html:158 +#: templates/recruitment/agency_portal_dashboard.html:162 +#, fuzzy +#| msgid "Deadline:" +msgid "Deadline" +msgstr "الموعد النهائي:" + +#: templates/jobs/job_list.html:273 +#, fuzzy +#| msgid "Candidate Form" +msgid "Manage Forms" +msgstr "نموذج المرشح" + +#: templates/jobs/job_list.html:276 +#, fuzzy +#| msgid "Applicants" +msgid "Applicants Metrics" +msgstr "المتقدمون" + +#: templates/jobs/job_list.html:281 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:65 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:78 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:45 +msgid "All" +msgstr "الكل" + +#: templates/jobs/job_list.html:282 +#: templates/jobs/partials/applicant_tracking.html:112 +msgid "Screened" +msgstr "" + +#: templates/jobs/job_list.html:355 +#, fuzzy +#| msgid "Offer Date" +msgid "Offers Made" +msgstr "تاريخ العرض" + +#: templates/jobs/job_list.html:356 +msgid "Form" +msgstr "" + +#: templates/jobs/job_list.html:359 +msgid "N/A" +msgstr "" + +#: templates/jobs/job_list.html:366 +#, fuzzy +#| msgid "Job Details" +msgid "Details" +msgstr "تفاصيل الوظيفة" + +#: templates/jobs/job_list.html:369 +msgid "Edit Job" +msgstr "تعديل الوظيفة" + +#: templates/jobs/job_list.html:392 +#, fuzzy +#| msgid "No meetings found." +msgid "No job postings found" +msgstr "لم يتم العثور على اجتماعات." + +#: templates/jobs/job_list.html:393 +msgid "Create your first job posting to get started or adjust your filters." +msgstr "" + +#: templates/meetings/create_meeting.html:4 +msgid "Create Zoom Meeting" +msgstr "إنشاء اجتماع Zoom" + +#: templates/meetings/create_meeting.html:151 +msgid "Create New Zoom Meeting" +msgstr "إنشاء اجتماع Zoom جديد" + +#: templates/meetings/create_meeting.html:155 +#: templates/meetings/meeting_details.html:210 +msgid "Back to Meetings" +msgstr "العودة إلى الاجتماعات" + +#: templates/meetings/delete_meeting_form.html:4 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "" +"Are you sure you want to delete this meeting? This action is irreversible." +msgstr "هل أنت متأكد من رغبتك في حذف هذه المادة؟" + +#: templates/meetings/delete_meeting_form.html:7 +#: templates/meetings/meeting_details.html:222 +msgid "Delete Meeting" +msgstr "حذف الاجتماع" + +#: templates/meetings/list_meetings.html:4 +#: templates/meetings/list_meetings.html:176 +msgid "Zoom Meetings" +msgstr "اجتماعات Zoom" + +#: templates/meetings/list_meetings.html:187 +msgid "Search by Topic" +msgstr "" + +#: templates/meetings/list_meetings.html:209 +#: templates/meetings/meeting_details.html:252 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Name" +msgstr "نموذج المرشح" + +#: templates/meetings/list_meetings.html:210 +#, fuzzy +#| msgid "Search templates by name..." +msgid "Search by candidate..." +msgstr "البحث عن القوالب بالاسم..." + +#: templates/meetings/list_meetings.html:264 +#: templates/meetings/list_meetings.html:309 +#: templates/user/admin_settings.html:173 +msgid "ID" +msgstr "المعرف" + +#: templates/meetings/list_meetings.html:265 +#, fuzzy +#| msgid "Started" +msgid "Start" +msgstr "بدأ" + +#: templates/meetings/list_meetings.html:277 +#: templates/meetings/list_meetings.html:362 +#, fuzzy +#| msgid "Join URL" +msgid "Join" +msgstr "رابط الانضمام" + +#: templates/meetings/list_meetings.html:423 +#, fuzzy +#| msgid "No meetings found." +msgid "No Zoom meetings found" +msgstr "لم يتم العثور على اجتماعات." + +#: templates/meetings/list_meetings.html:424 +msgid "Create your first meeting or adjust your filters." +msgstr "" + +#: templates/meetings/list_meetings.html:426 +msgid "Create Your First Meeting" +msgstr "إنشاء أول اجتماع لك" + +#: templates/meetings/meeting_details.html:216 +#, fuzzy +#| msgid "Update Meeting" +msgid "Edit Meeting" +msgstr "تحديث الاجتماع" + +#: templates/meetings/meeting_details.html:221 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "Are you sure you want to delete this meeting? This action is permanent." +msgstr "هل أنت متأكد من رغبتك في حذف هذه المادة؟" + +#: templates/meetings/meeting_details.html:249 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Detail" +msgstr "تاريخ المقابلة" + +#: templates/meetings/meeting_details.html:253 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Email" +msgstr "نموذج المرشح" + +#: templates/meetings/meeting_details.html:262 +#, fuzzy +#| msgid "Core Details" +msgid "Connection Details" +msgstr "التفاصيل الأساسية" + +#: templates/meetings/meeting_details.html:264 +#, fuzzy +#| msgid "Start Time" +msgid "Date & Time" +msgstr "وقت البدء" + +#: templates/meetings/meeting_details.html:265 +#: templates/recruitment/notification_detail.html:71 +msgid "minutes" +msgstr "" + +#: templates/meetings/meeting_details.html:267 +msgid "Host Email" +msgstr "بريد المضيف الإلكتروني" + +#: templates/meetings/meeting_details.html:277 +#, fuzzy +#| msgid "Join URL" +msgid "Copy URL" +msgstr "رابط الانضمام" + +#: templates/meetings/meeting_details.html:298 +msgid "Assigned Participants" +msgstr "" + +#: templates/meetings/meeting_details.html:304 +#, fuzzy +#| msgid "Toggle navigation" +msgid "Role/Designation" +msgstr "تبديل التنقل" + +#: templates/meetings/meeting_details.html:317 +msgid "External Participants" +msgstr "" + +#: templates/meetings/meeting_details.html:326 +msgid "System User" +msgstr "" + +#: templates/meetings/meeting_details.html:346 +msgid "Comments" +msgstr "التعليقات" + +#: templates/meetings/meeting_details.html:369 +#: templates/meetings/meeting_details.html:391 +#, fuzzy +#| msgid "Edit Job" +msgid "Edit Comment" +msgstr "تعديل الوظيفة" + +#: templates/meetings/meeting_details.html:376 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Delete Comment" +msgstr "حذف المرشح" + +#: templates/meetings/meeting_details.html:376 +#, fuzzy +#| msgid "Are you sure you want to delete this item?" +msgid "Are you sure you want to delete this comment?" +msgstr "هل أنت متأكد من رغبتك في حذف هذا العنصر؟" + +#: templates/meetings/meeting_details.html:407 +msgid "No comments yet. Be the first to comment!" +msgstr "" + +#: templates/meetings/meeting_details.html:414 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add a New Comment" +msgstr "إضافة مرشح جديد" + +#: templates/meetings/meeting_details.html:427 +#, fuzzy +#| msgid "Submit" +msgid "Submit Comment" +msgstr "إرسال" + +#: templates/meetings/meeting_details.html:431 +msgid "You must be logged in to add a comment." +msgstr "" + +#: templates/meetings/meeting_details.html:475 +#, fuzzy +#| msgid "Failed" +msgid "Copy Failed." +msgstr "رسب" + +#: templates/meetings/reschedule_meeting.html:9 +#: templates/meetings/schedule_meeting_form.html:7 +#: templates/recruitment/schedule_meeting_form.html:13 +#, fuzzy +#| msgid "Interview" +msgid "Update Interview" +msgstr "المقابلة" + +#: templates/meetings/reschedule_meeting.html:15 +msgid "You are updating the existing meeting schedule." +msgstr "" + +#: templates/meetings/reschedule_meeting.html:27 +#: templates/meetings/schedule_meeting_form.html:27 +#: templates/recruitment/schedule_meeting_form.html:38 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting Topic" +msgstr "معرف الاجتماع" + +#: templates/meetings/reschedule_meeting.html:65 +#: templates/meetings/schedule_meeting_form.html:82 +#: templates/meetings/update_meeting.html:233 +msgid "Update Meeting" +msgstr "تحديث الاجتماع" + +#: templates/meetings/schedule_meeting_form.html:16 +msgid "Candidate has upcoming interviews. Updating existing schedule." +msgstr "" + +#: templates/meetings/schedule_meeting_form.html:38 +msgid "e.g., Technical Screening, HR Interview" +msgstr "" + +#: templates/meetings/set_candidate_form.html:5 +#: templates/recruitment/candidate_interview_view.html:509 +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/change_list.html:41 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:23 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:24 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/pagination.html:19 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:9 +msgid "Save" +msgstr "حفظ" + +#: templates/meetings/update_meeting.html:4 +#: templates/meetings/update_meeting.html:196 +msgid "Update Zoom Meeting" +msgstr "تحديث اجتماع Zoom" + +#: templates/meetings/update_meeting.html:198 +msgid "Modify the details of your scheduled meeting" +msgstr "تعديل تفاصيل اجتماعك المجدول" + +#: templates/meetings/update_meeting.html:207 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Details" +msgstr "العودة إلى الاجتماعات" + +#: templates/participants/participants_create.html:94 +#, fuzzy +#| msgid "Create New Candidate" +msgid "Create New Participant" +msgstr "إنشاء مرشح جديد" + +#: templates/participants/participants_create.html:96 +#, fuzzy +#| msgid "Enter details to create a new candidate record." +msgid "Enter details to create a new participant record." +msgstr "أدخل التفاصيل لإنشاء سجل مرشح جديد." + +#: templates/participants/participants_create.html:99 +#: templates/participants/participants_create.html:101 +#: templates/participants/participants_detail.html:135 +#: templates/participants/participants_detail.html:136 +#: templates/recruitment/candidate_create.html:99 +#: templates/recruitment/candidate_create.html:101 +#: templates/recruitment/candidate_detail.html:631 +#: templates/recruitment/candidate_update.html:97 +#: templates/recruitment/candidate_update.html:99 +#: templates/recruitment/training_create.html:112 +#: templates/recruitment/training_create.html:114 +#: templates/recruitment/training_update.html:112 +#: templates/recruitment/training_update.html:114 +msgid "Back to List" +msgstr "العودة إلى القائمة" + +#: templates/participants/participants_create.html:112 +#, fuzzy +#| msgid "Basic Information" +msgid "Participant Information" +msgstr "المعلومات الأساسية" + +#: templates/participants/participants_create.html:131 +#, fuzzy +#| msgid "Participant Video" +msgid "Save Participant" +msgstr "فيديو المشارك" + +#: templates/participants/participants_detail.html:131 +#, fuzzy +#| msgid "Participant Video" +msgid "Participant Details" +msgstr "فيديو المشارك" + +#: templates/participants/participants_detail.html:139 +#, fuzzy +#| msgid "Participant Video" +msgid "Edit Participant" +msgstr "فيديو المشارك" + +#: templates/participants/participants_detail.html:140 +#, fuzzy +#| msgid "Profile" +msgid "Edit Profile" +msgstr "الملف الشخصي" + +#: templates/participants/participants_detail.html:161 +#, fuzzy +#| msgid "Candidate Information" +msgid "Contact & Role Information" +msgstr "معلومات المرشح" + +#: templates/participants/participants_detail.html:166 +#: templates/user/admin_settings.html:174 +#, fuzzy +#| msgid "First Name" +msgid "Full Name" +msgstr "الاسم الأول" + +#: templates/participants/participants_detail.html:192 +#, fuzzy +#| msgid "Active Jobs" +msgid "Assigned Jobs" +msgstr "الوظائف النشطة" + +#: templates/participants/participants_detail.html:199 +msgid "This participant is not currently assigned to any job." +msgstr "" + +#: templates/participants/participants_detail.html:210 +msgid "Metadata" +msgstr "" + +#: templates/participants/participants_detail.html:213 +#, fuzzy +#| msgid "Created" +msgid "Record Created" +msgstr "تم الإنشاء" + +#: templates/participants/participants_detail.html:214 +#: templates/participants/participants_detail.html:219 +msgid "at" +msgstr "" + +#: templates/participants/participants_detail.html:225 +msgid "Total Assigned Jobs" +msgstr "" + +#: templates/participants/participants_detail.html:240 +#: templates/participants/participants_list.html:322 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm Deletion" +msgstr "تأكيد الحذف" + +#: templates/participants/participants_detail.html:244 +#: templates/participants/participants_list.html:326 +#, fuzzy +#| msgid "Are you sure you want to delete this item?" +msgid "Are you sure you want to delete" +msgstr "هل أنت متأكد من رغبتك في حذف هذا العنصر؟" + +#: templates/participants/participants_detail.html:248 +#: templates/participants/participants_list.html:330 +msgid "This action cannot be undone." +msgstr "" + +#: templates/participants/participants_list.html:143 +#, fuzzy +#| msgid "Participant Video" +msgid "Participants List" +msgstr "فيديو المشارك" + +#: templates/participants/participants_list.html:147 +#, fuzzy +#| msgid "Add New Material" +msgid "Add New Participant" +msgstr "إضافة مادة جديدة" + +#: templates/participants/participants_list.html:156 +#: templates/recruitment/candidate_list.html:206 +msgid "Search by Name or Email" +msgstr "" + +#: templates/participants/participants_list.html:172 +msgid "Filter by Assigned Job" +msgstr "" + +#: templates/participants/participants_list.html:174 +#: templates/recruitment/candidate_list.html:224 +#: templates/recruitment/dashboard.html:395 +#, fuzzy +#| msgid "Jobs" +msgid "All Jobs" +msgstr "الوظائف" + +#: templates/participants/participants_list.html:304 +#, fuzzy +#| msgid "No candidates found." +msgid "No participants found" +msgstr "لم يتم العثور على مرشحين." + +#: templates/participants/participants_list.html:305 +msgid "Create your first participant record or adjust your filters." +msgstr "" + +#: templates/participants/participants_list.html:308 +#, fuzzy +#| msgid "Participant Video" +msgid "Add Participant" +msgstr "فيديو المشارك" + +#: templates/recruitment/agency_access_link_detail.html:4 +#: templates/recruitment/agency_access_link_detail.html:12 +#, fuzzy +#| msgid "Meeting Details" +msgid "Access Link Details" +msgstr "تفاصيل الاجتماع" + +#: templates/recruitment/agency_access_link_detail.html:14 +msgid "Secure access link for agency candidate submissions" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:17 +#: templates/recruitment/agency_portal_submit_candidate.html:122 +#: templates/recruitment/agency_portal_submit_candidate.html:365 +#, fuzzy +#| msgid "Back to List" +msgid "Back to Assignment" +msgstr "العودة إلى القائمة" + +#: templates/recruitment/agency_access_link_detail.html:28 +#, fuzzy +#| msgid "Basic Information" +msgid "Access Information" +msgstr "المعلومات الأساسية" + +#: templates/recruitment/agency_access_link_detail.html:31 +#: templates/user/admin_settings.html:194 +#, fuzzy +#| msgid "Active" +msgid "Inactive" +msgstr "نشط" + +#: templates/recruitment/agency_access_link_detail.html:66 +#, fuzzy +#| msgid "Candidates" +msgid "Max Candidates" +msgstr "المرشحون" + +#: templates/recruitment/agency_access_link_detail.html:83 +#: templates/recruitment/agency_assignment_detail.html:172 +msgid "Access Credentials" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:87 +#: templates/recruitment/agency_assignment_detail.html:176 +#, fuzzy +#| msgid "Join URL" +msgid "Login URL" +msgstr "رابط الانضمام" + +#: templates/recruitment/agency_access_link_detail.html:121 +#: templates/recruitment/agency_assignment_detail.html:210 +msgid "" +"Share these credentials securely with the agency. They can use this " +"information to log in and submit candidates." +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:132 +msgid "Usage Statistics" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:137 +msgid "Total Accesses" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:146 +msgid "Never" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:173 +msgid "View Assignment" +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:178 +#: templates/user/admin_settings.html:232 +#, fuzzy +#| msgid "Active" +msgid "Deactivate" +msgstr "نشط" + +#: templates/recruitment/agency_access_link_detail.html:184 +#, fuzzy +#| msgid "Active" +msgid "Reactivate" +msgstr "نشط" + +#: templates/recruitment/agency_access_link_detail.html:217 +#: templates/recruitment/agency_assignment_detail.html:489 +msgid "" +"Are you sure you want to deactivate this access link? Agencies will no " +"longer be able to use it." +msgstr "" + +#: templates/recruitment/agency_access_link_detail.html:224 +#: templates/recruitment/agency_assignment_detail.html:496 +#, fuzzy +#| msgid "Are you sure you want to delete this material?" +msgid "Are you sure you want to reactivate this access link?" +msgstr "هل أنت متأكد من رغبتك في حذف هذه المادة؟" + +#: templates/recruitment/agency_access_link_form.html:14 +msgid "Generate a secure access link for agency to submit candidates" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:17 +#: templates/recruitment/agency_assignment_detail.html:103 +#: templates/recruitment/agency_assignment_form.html:114 +#, fuzzy +#| msgid "Back to List" +msgid "Back to Assignments" +msgstr "العودة إلى القائمة" + +#: templates/recruitment/agency_access_link_form.html:47 +msgid "Select the agency job assignment" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:62 +msgid "When will this access link expire?" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:69 +msgid "Max Submissions" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:79 +msgid "" +"Maximum number of candidates agency can submit (leave blank for unlimited)" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:99 +msgid "Whether this access link is currently active" +msgstr "" + +#: templates/recruitment/agency_access_link_form.html:105 +msgid "Notes" +msgstr "ملاحظات" + +#: templates/recruitment/agency_access_link_form.html:115 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Additional notes or instructions for the agency" +msgstr "ملاحظات داخلية حول الوكالة" + +#: templates/recruitment/agency_access_link_form.html:122 +msgid "" +"Access links will be generated with a secure token that agencies can use to " +"log in" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:98 +msgid "Assignment Details and Management" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:106 +#: templates/recruitment/agency_assignment_detail.html:357 +msgid "Edit Assignment" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:118 +#: templates/recruitment/agency_portal_assignment_detail.html:110 +#: templates/recruitment/agency_portal_assignment_detail.html:137 +#: templates/recruitment/agency_portal_submit_candidate.html:133 +#, fuzzy +#| msgid "Edit Details" +msgid "Assignment Details" +msgstr "تعديل التفاصيل" + +#: templates/recruitment/agency_assignment_detail.html:215 +#, fuzzy +#| msgid "Meeting Details" +msgid "View Access Links Details" +msgstr "تفاصيل الاجتماع" + +#: templates/recruitment/agency_assignment_detail.html:225 +#: templates/recruitment/agency_portal_assignment_detail.html:231 +#, fuzzy +#| msgid "New Candidates" +msgid "Submitted Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/agency_assignment_detail.html:229 +#, fuzzy +#| msgid "Preview" +msgid "Preview Portal" +msgstr "معاينة" + +#: templates/recruitment/agency_assignment_detail.html:240 +#: templates/recruitment/agency_portal_assignment_detail.html:242 +#: templates/recruitment/candidate_exam_view.html:251 +#: templates/recruitment/candidate_hired_view.html:233 +#: templates/recruitment/candidate_interview_view.html:262 +#: templates/recruitment/candidate_offer_view.html:247 +#, fuzzy +#| msgid "Content" +msgid "Contact" +msgstr "المحتوى" + +#: templates/recruitment/agency_assignment_detail.html:242 +#: templates/recruitment/agency_portal_assignment_detail.html:244 +#, fuzzy +#| msgid "Submit" +msgid "Submitted" +msgstr "إرسال" + +#: templates/recruitment/agency_assignment_detail.html:280 +#: templates/recruitment/agency_portal_assignment_detail.html:319 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates submitted yet" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/agency_assignment_detail.html:282 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "" +"Candidates will appear here once the agency submits them through their " +"portal." +msgstr "سيظهر المرشحون هنا بمجرد تقديمهم لهذا المنصب." + +#: templates/recruitment/agency_assignment_detail.html:294 +#: templates/recruitment/agency_portal_assignment_detail.html:333 +#: templates/recruitment/agency_portal_dashboard.html:181 +msgid "Submission Progress" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:325 +#: templates/recruitment/agency_portal_assignment_detail.html:175 +#: templates/recruitment/agency_portal_assignment_detail.html:364 +#, fuzzy +#| msgid "Candidates" +msgid "candidates" +msgstr "المرشحون" + +#: templates/recruitment/agency_assignment_detail.html:345 +#: templates/recruitment/agency_detail.html:492 +#: templates/recruitment/agency_portal_assignment_detail.html:513 +#, fuzzy +#| msgid "Error Message" +msgid "Send Message" +msgstr "رسالة الخطأ" + +#: templates/recruitment/agency_assignment_detail.html:351 +#: templates/recruitment/agency_assignment_detail.html:433 +#, fuzzy +#| msgid "Deadline:" +msgid "Extend Deadline" +msgstr "الموعد النهائي:" + +#: templates/recruitment/agency_assignment_detail.html:369 +#: templates/recruitment/agency_portal_assignment_detail.html:437 +msgid "Recent Messages" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:381 +#: templates/recruitment/agency_portal_assignment_detail.html:449 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:186 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:224 +msgid "From" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:385 +#: templates/recruitment/agency_portal_assignment_detail.html:453 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:22 +msgid "New" +msgstr "جديد" + +#: templates/recruitment/agency_assignment_detail.html:395 +#: templates/recruitment/agency_portal_assignment_detail.html:463 +#, fuzzy +#| msgid "View API Response" +msgid "View All Messages" +msgstr "عرض استجابة واجهة برمجة التطبيقات" + +#: templates/recruitment/agency_assignment_detail.html:410 +msgid "Extend Assignment Deadline" +msgstr "" + +#: templates/recruitment/agency_assignment_detail.html:419 +#, fuzzy +#| msgid "Deadline:" +msgid "New Deadline" +msgstr "الموعد النهائي:" + +#: templates/recruitment/agency_assignment_detail.html:424 +#, fuzzy +#| msgid "Deadline:" +msgid "Current deadline:" +msgstr "الموعد النهائي:" + +#: templates/recruitment/agency_assignment_detail.html:457 +msgid "Token copied to clipboard!" +msgstr "" + +#: templates/recruitment/agency_assignment_form.html:110 +msgid "Assign a job to an external hiring agency" +msgstr "" + +#: templates/recruitment/agency_assignment_form.html:170 +msgid "Maximum number of candidates the agency can submit" +msgstr "" + +#: templates/recruitment/agency_assignment_form.html:187 +msgid "Date and time when submission period ends" +msgstr "" + +#: templates/recruitment/agency_assignment_form.html:207 +#, fuzzy +#| msgid "Internal notes about the agency" +msgid "Internal notes about this assignment (not visible to agency)" +msgstr "ملاحظات داخلية حول الوكالة" + +#: templates/recruitment/agency_assignment_list.html:4 +#: templates/recruitment/agency_assignment_list.html:59 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency Assignments" +msgstr "اسم الوكالة" + +#: templates/recruitment/agency_assignment_list.html:62 +msgid "Total Assignments:" +msgstr "" + +#: templates/recruitment/agency_assignment_list.html:67 +msgid "New Assignment" +msgstr "" + +#: templates/recruitment/agency_assignment_list.html:79 +msgid "Search by agency or job title..." +msgstr "" + +#: templates/recruitment/agency_assignment_list.html:169 +#, fuzzy +#| msgid "View on LinkedIn" +msgid "View Access Link" +msgstr "عرض على LinkedIn" + +#: templates/recruitment/agency_assignment_list.html:183 +msgid "Assignments pagination" +msgstr "" + +#: templates/recruitment/agency_assignment_list.html:228 +#, fuzzy +#| msgid "No meetings found." +msgid "No assignments found" +msgstr "لم يتم العثور على اجتماعات." + +#: templates/recruitment/agency_assignment_list.html:229 +msgid "Create your first agency assignment to get started." +msgstr "" + +#: templates/recruitment/agency_assignment_list.html:231 +msgid "Create Assignment" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:4 +#: templates/recruitment/agency_confirm_delete.html:179 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Delete Agency" +msgstr "حذف الاجتماع" + +#: templates/recruitment/agency_confirm_delete.html:182 +msgid "You are about to delete a hiring agency. This action cannot be undone." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:186 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Agency" +msgstr "العودة إلى الاجتماعات" + +#: templates/recruitment/agency_confirm_delete.html:197 +msgid "Warning: This action cannot be undone!" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:199 +msgid "" +"Deleting this agency will permanently remove all associated data. Please " +"review the information below carefully before proceeding." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:208 +msgid "Agency to be Deleted" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:277 +#, fuzzy +#| msgid "No candidates found." +msgid "Associated Candidates Found" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/agency_confirm_delete.html:280 +msgid "candidate(s) are associated with this agency." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:283 +msgid "" +"Deleting this agency will affect these candidates. Their agency reference " +"will be removed, but the candidates themselves will not be deleted." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:293 +msgid "What will happen when you delete this agency?" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:300 +msgid "The agency profile and all its information will be permanently deleted" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:304 +#, fuzzy +#| msgid "Edit candidate information and details" +msgid "All contact information and agency details will be removed" +msgstr "تعديل معلومات المرشح وتفاصيله" + +#: templates/recruitment/agency_confirm_delete.html:309 +msgid "Associated candidates will lose their agency reference" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:313 +msgid "Historical data linking candidates to this agency will be lost" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:318 +msgid "This action cannot be undone under any circumstances" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:332 +msgid "Type the agency name to confirm deletion:" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:341 +msgid "This is required to prevent accidental deletions." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:349 +msgid "" +"I understand that this action cannot be undone and I want to permanently " +"delete this agency." +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:364 +msgid "Delete Agency Permanently" +msgstr "" + +#: templates/recruitment/agency_confirm_delete.html:402 +#, fuzzy +#| msgid "Are you sure you want to delete this candidate?" +msgid "" +"Are you absolutely sure you want to delete this agency? This action cannot " +"be undone." +msgstr "هل أنت متأكد من رغبتك في حذف هذا المرشح؟" + +#: templates/recruitment/agency_detail.html:4 +#, fuzzy +#| msgid "Core Details" +msgid "Agency Details" +msgstr "التفاصيل الأساسية" + +#: templates/recruitment/agency_detail.html:220 +msgid "Hiring Agency Details and Candidate Management" +msgstr "" + +#: templates/recruitment/agency_detail.html:225 +msgid "Assign job" +msgstr "" + +#: templates/recruitment/agency_detail.html:228 +#: templates/recruitment/agency_detail.html:486 +#, fuzzy +#| msgid "Hiring Agency" +msgid "Edit Agency" +msgstr "وكالة التوظيف" + +#: templates/recruitment/agency_detail.html:231 +#: templates/recruitment/agency_form.html:131 +#, fuzzy +#| msgid "Back to List" +msgid "Back to Agencies" +msgstr "العودة إلى القائمة" + +#: templates/recruitment/agency_detail.html:248 +#: templates/recruitment/agency_list.html:285 +#, fuzzy +#| msgid "Contact & Job" +msgid "Contact:" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/agency_detail.html:280 +#: templates/recruitment/agency_form.html:216 +#: templates/recruitment/agency_portal_submit_candidate.html:214 +#, fuzzy +#| msgid "Join Information" +msgid "Contact Information" +msgstr "معلومات الانضمام" + +#: templates/recruitment/agency_detail.html:330 +#: templates/recruitment/agency_form.html:273 +#, fuzzy +#| msgid "Application Information" +msgid "Location Information" +msgstr "معلومات التقديم" + +#: templates/recruitment/agency_detail.html:391 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Recent Candidates" +msgstr "حذف المرشح" + +#: templates/recruitment/agency_detail.html:394 +#: templates/recruitment/agency_detail.html:489 +#, fuzzy +#| msgid "All Candidates" +msgid "View All Candidates" +msgstr "جميع المرشحين" + +#: templates/recruitment/agency_detail.html:427 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates yet" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/agency_detail.html:428 +msgid "This agency hasn't submitted any candidates yet." +msgstr "" + +#: templates/recruitment/agency_detail.html:442 +#, fuzzy +#| msgid "Candidates" +msgid "Candidate Statistics" +msgstr "المرشحون" + +#: templates/recruitment/agency_detail.html:450 +msgid "Total" +msgstr "" + +#: templates/recruitment/agency_detail.html:480 +#, fuzzy +#| msgid "Actions" +msgid "Quick Actions" +msgstr "الإجراءات" + +#: templates/recruitment/agency_detail.html:496 +msgid "Visit Website" +msgstr "" + +#: templates/recruitment/agency_detail.html:513 +#: templates/recruitment/agency_form.html:390 +#, fuzzy +#| msgid "Join Information" +msgid "Agency Information" +msgstr "معلومات الانضمام" + +#: templates/recruitment/agency_detail.html:522 +#: templates/recruitment/agency_form.html:397 +msgid "Last Updated:" +msgstr "آخر تحديث:" + +#: templates/recruitment/agency_detail.html:526 +#, fuzzy +#| msgid "Agency Name" +msgid "Agency ID:" +msgstr "اسم الوكالة" + +#: templates/recruitment/agency_form.html:124 +msgid "Update the hiring agency information below." +msgstr "" + +#: templates/recruitment/agency_form.html:126 +msgid "Fill in the details to add a new hiring agency." +msgstr "" + +#: templates/recruitment/agency_form.html:144 +msgid "Please correct the errors below:" +msgstr "" + +#: templates/recruitment/agency_form.html:159 +msgid "Basic Information" +msgstr "المعلومات الأساسية" + +#: templates/recruitment/agency_form.html:313 +#, fuzzy +#| msgid "Application Information" +msgid "Additional Information" +msgstr "معلومات التقديم" + +#: templates/recruitment/agency_form.html:362 +msgid "Quick Tips" +msgstr "" + +#: templates/recruitment/agency_form.html:367 +msgid "Provide accurate contact information for better communication" +msgstr "" + +#: templates/recruitment/agency_form.html:371 +msgid "Include a valid website URL if available" +msgstr "" + +#: templates/recruitment/agency_form.html:375 +msgid "Add a detailed description to help identify the agency" +msgstr "" + +#: templates/recruitment/agency_form.html:379 +msgid "All fields marked with * are required" +msgstr "" + +#: templates/recruitment/agency_form.html:401 +#, fuzzy +#| msgid "Slug" +msgid "Slug:" +msgstr "الرابط المختصر" + +#: templates/recruitment/agency_list.html:134 +#, fuzzy +#| msgid "Hiring Agencies" +msgid "Total Agencies:" +msgstr "وكالات التوظيف" + +#: templates/recruitment/agency_list.html:141 +#, fuzzy +#| msgid "View All Applicants" +msgid "View All Job Assignments" +msgstr "عرض جميع المتقدمين" + +#: templates/recruitment/agency_list.html:145 +#, fuzzy +#| msgid "Add New Material" +msgid "Add New Agency" +msgstr "إضافة مادة جديدة" + +#: templates/recruitment/agency_list.html:161 +msgid "Search by name, contact person, email, or country..." +msgstr "" + +#: templates/recruitment/agency_list.html:341 +msgid "Agency pagination" +msgstr "" + +#: templates/recruitment/agency_list.html:385 +#, fuzzy +#| msgid "No templates match your search \"%(query)s\"." +msgid "No agencies found matching your search criteria." +msgstr "لا توجد قوالب تطابق بحثك \"%(query)s\"." + +#: templates/recruitment/agency_list.html:387 +msgid "No hiring agencies have been added yet." +msgstr "" + +#: templates/recruitment/agency_list.html:391 +msgid "" +"Start by adding your first hiring agency to manage your recruitment partners." +msgstr "" + +#: templates/recruitment/agency_list.html:394 +#, fuzzy +#| msgid "Add Your First Candidate" +msgid "Add Your First Agency" +msgstr "إضافة أول مرشح لك" + +#: templates/recruitment/agency_portal_assignment_detail.html:115 +#, fuzzy +#| msgid "Dashboard" +msgid "Back to Dashboard" +msgstr "لوحة التحكم" + +#: templates/recruitment/agency_portal_assignment_detail.html:118 +#: templates/recruitment/agency_portal_submit_candidate.html:114 +#, fuzzy +#| msgid "New Candidates" +msgid "Submit New Candidate" +msgstr "المرشحون الجدد" + +#: templates/recruitment/agency_portal_assignment_detail.html:169 +#, fuzzy +#| msgid "Training" +msgid "days remaining" +msgstr "التدريب" + +#: templates/recruitment/agency_portal_assignment_detail.html:182 +#, fuzzy +#| msgid "Job Description" +msgid "Job Description " +msgstr "وصف الوظيفة" + +#: templates/recruitment/agency_portal_assignment_detail.html:269 +#: templates/recruitment/agency_portal_assignment_detail.html:528 +#, fuzzy +#| msgid "Update Candidate" +msgid "Edit Candidate" +msgstr "تحديث المرشح" + +#: templates/recruitment/agency_portal_assignment_detail.html:272 +#: templates/recruitment/agency_portal_assignment_detail.html:593 +#: templates/recruitment/agency_portal_assignment_detail.html:612 +#, fuzzy +#| msgid "Create Candidate" +msgid "Remove Candidate" +msgstr "إنشاء مرشح" + +#: templates/recruitment/agency_portal_assignment_detail.html:321 +msgid "Submit candidates using the form above to get started." +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:374 +#: templates/recruitment/agency_portal_submit_candidate.html:156 +#, fuzzy +#| msgid "Submit" +msgid "Can Submit" +msgstr "إرسال" + +#: templates/recruitment/agency_portal_assignment_detail.html:376 +#: templates/recruitment/agency_portal_submit_candidate.html:158 +#, fuzzy +#| msgid "Submit" +msgid "Cannot Submit" +msgstr "إرسال" + +#: templates/recruitment/agency_portal_assignment_detail.html:406 +msgid "Assignment Info" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:415 +msgid "Days Remaining" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:417 +#: templates/recruitment/agency_portal_submit_candidate.html:147 +msgid "days" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:422 +msgid "Submission Rate" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:478 +msgid "Send Message to Admin" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:493 +msgid "Priority" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:495 +msgid "Low" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:496 +msgid "Medium" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:497 +msgid "High" +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:498 +#, fuzzy +#| msgid "User Agent" +msgid "Urgent" +msgstr "وكيل المستخدم" + +#: templates/recruitment/agency_portal_assignment_detail.html:603 +#, fuzzy +#| msgid "Are you sure you want to delete this candidate?" +msgid "" +"Are you sure you want to remove this candidate? This action cannot be undone." +msgstr "هل أنت متأكد من رغبتك في حذف هذا المرشح؟" + +#: templates/recruitment/agency_portal_assignment_detail.html:605 +#, fuzzy +#| msgid "Candidate" +msgid "Candidate:" +msgstr "المرشح" + +#: templates/recruitment/agency_portal_assignment_detail.html:644 +msgid "Error loading candidate data. Please try again." +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:679 +#: templates/recruitment/agency_portal_assignment_detail.html:684 +msgid "Error updating candidate. Please try again." +msgstr "" + +#: templates/recruitment/agency_portal_assignment_detail.html:706 +#: templates/recruitment/agency_portal_assignment_detail.html:711 +msgid "Error removing candidate. Please try again." +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:4 +#: templates/recruitment/agency_portal_dashboard.html:45 +#, fuzzy +#| msgid "Dashboard" +msgid "Agency Dashboard" +msgstr "لوحة التحكم" + +#: templates/recruitment/agency_portal_dashboard.html:48 +msgid "Welcome back" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:76 +msgid "Total Assignments" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:87 +msgid "Active Assignments" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:98 +#: templates/recruitment/partials/stats_cards.html:28 +#, fuzzy +#| msgid "All Candidates" +msgid "Total Candidates" +msgstr "جميع المرشحين" + +#: templates/recruitment/agency_portal_dashboard.html:109 +#, fuzzy +#| msgid "Error Message" +msgid "Unread Messages" +msgstr "رسالة الخطأ" + +#: templates/recruitment/agency_portal_dashboard.html:121 +msgid "Your Job Assignments" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:123 +msgid "assignments" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:166 +msgid "days left" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:168 +msgid "days overdue" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:202 +msgid "Submissions Closed" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:230 +msgid "No Job Assignments Found" +msgstr "" + +#: templates/recruitment/agency_portal_dashboard.html:232 +msgid "" +"You don't have any job assignments yet. Please contact the administrator if " +"you expect to have assignments." +msgstr "" + +#: templates/recruitment/agency_portal_login.html:4 +msgid "Agency Portal Login" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:128 +#, fuzzy +#| msgid "Edit candidate information and details" +msgid "Submit candidates for job assignments" +msgstr "تعديل معلومات المرشح وتفاصيله" + +#: templates/recruitment/agency_portal_login.html:159 +msgid "Enter the access token provided by the hiring organization" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:181 +msgid "Enter the password for this access token" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:189 +msgid "Access Portal" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:198 +msgid "Need Help?" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:206 +#, fuzzy +#| msgid "Contact & Job" +msgid "Contact Support" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/agency_portal_login.html:208 +msgid "Reach out to your hiring contact" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:215 +#, fuzzy +#| msgid "Duration" +msgid "Documentation" +msgstr "المدة" + +#: templates/recruitment/agency_portal_login.html:217 +msgid "View user guides and tutorials" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:227 +msgid "Security Notice" +msgstr "" + +#: templates/recruitment/agency_portal_login.html:230 +msgid "" +"This portal is for authorized agency partners only. Access is monitored and " +"logged." +msgstr "" + +#: templates/recruitment/agency_portal_login.html:234 +msgid "" +"If you believe you've received this link in error, please contact the hiring " +"organization immediately." +msgstr "" + +#: templates/recruitment/agency_portal_login.html:295 +msgid "Please enter your access token." +msgstr "" + +#: templates/recruitment/agency_portal_login.html:302 +msgid "Please enter your password." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:117 +#, fuzzy +#| msgid "No candidates found." +msgid "Submit a candidate for" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/agency_portal_submit_candidate.html:136 +#, fuzzy +#| msgid "Position No:" +msgid "Position:" +msgstr "رقم المنصب:" + +#: templates/recruitment/agency_portal_submit_candidate.html:145 +msgid "Days Remaining:" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:154 +#, fuzzy +#| msgid "Status" +msgid "Status:" +msgstr "الحالة" + +#: templates/recruitment/agency_portal_submit_candidate.html:170 +#: templates/recruitment/candidate_create.html:112 +msgid "Candidate Information" +msgstr "معلومات المرشح" + +#: templates/recruitment/agency_portal_submit_candidate.html:226 +#, fuzzy +#| msgid "Enter email" +msgid "Enter email address" +msgstr "أدخل البريد الإلكتروني" + +#: templates/recruitment/agency_portal_submit_candidate.html:246 +#, fuzzy +#| msgid "Candidate Information" +msgid "Address Information" +msgstr "معلومات المرشح" + +#: templates/recruitment/agency_portal_submit_candidate.html:251 +#, fuzzy +#| msgid "IP Address" +msgid "Full Address" +msgstr "عنوان IP" + +#: templates/recruitment/agency_portal_submit_candidate.html:258 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter full address" +msgstr "أدخل اسم العائلة" + +#: templates/recruitment/agency_portal_submit_candidate.html:267 +#, fuzzy +#| msgid "Resume" +msgid "Resume/CV" +msgstr "السيرة الذاتية" + +#: templates/recruitment/agency_portal_submit_candidate.html:272 +#, fuzzy +#| msgid "Download Resume" +msgid "Upload Resume" +msgstr "تحميل السيرة الذاتية" + +#: templates/recruitment/agency_portal_submit_candidate.html:284 +msgid "Click to upload or drag and drop" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:286 +msgid "Accepted formats: PDF, DOC, DOCX (Maximum 5MB)" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:294 +msgid "Remove File" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:306 +msgid "Additional Notes" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:311 +msgid "Notes (Optional)" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:317 +msgid "Any additional information about the candidate" +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:328 +msgid "Submitted candidates will be reviewed by the hiring team." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:349 +#, fuzzy +#| msgid "Create Candidate" +msgid "Cannot Submit Candidates" +msgstr "إنشاء مرشح" + +#: templates/recruitment/agency_portal_submit_candidate.html:353 +msgid "This assignment has expired. Submissions are no longer accepted." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:356 +msgid "Maximum candidate limit reached for this assignment." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:359 +msgid "This assignment is not currently active." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:383 +msgid "Submitting candidate..." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:384 +msgid "Please wait while we process your submission." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:450 +msgid "Please upload a PDF, DOC, or DOCX file." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:457 +msgid "File size must be less than 5MB." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:478 +#, fuzzy +#| msgid "Submit" +msgid "Submitting..." +msgstr "إرسال" + +#: templates/recruitment/agency_portal_submit_candidate.html:500 +#, fuzzy +#| msgid "Posted successfully!" +msgid "Candidate submitted successfully!" +msgstr "تم النشر بنجاح!" + +#: templates/recruitment/agency_portal_submit_candidate.html:531 +msgid "Error submitting candidate. Please try again." +msgstr "" + +#: templates/recruitment/agency_portal_submit_candidate.html:553 +msgid "Network error. Please check your connection and try again." +msgstr "" + +#: templates/recruitment/candidate_create.html:94 +msgid "Create New Candidate" +msgstr "إنشاء مرشح جديد" + +#: templates/recruitment/candidate_create.html:96 +msgid "Enter details to create a new candidate record." +msgstr "أدخل التفاصيل لإنشاء سجل مرشح جديد." + +#: templates/recruitment/candidate_create.html:131 +msgid "Create Candidate" +msgstr "إنشاء مرشح" + +#: templates/recruitment/candidate_detail.html:292 +msgid "Stage:" +msgstr "المرحلة:" + +#: templates/recruitment/candidate_detail.html:297 +msgid "Applied for:" +msgstr "تقدم لـ:" + +#: templates/recruitment/candidate_detail.html:303 +#: templates/recruitment/candidate_exam_view.html:229 +#: templates/recruitment/candidate_interview_view.html:222 +#: templates/recruitment/candidate_offer_view.html:222 +#: templates/recruitment/candidate_screening_view.html:341 +msgid "Change Stage" +msgstr "تغيير المرحلة" + +#: templates/recruitment/candidate_detail.html:313 +msgid "Contact & Job" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/candidate_detail.html:320 +msgid "Journey Timeline" +msgstr "" + +#: templates/recruitment/candidate_detail.html:331 +msgid "Core Details" +msgstr "التفاصيل الأساسية" + +#: templates/recruitment/candidate_detail.html:346 +msgid "Position Applied" +msgstr "المنصب المتقدم له" + +#: templates/recruitment/candidate_detail.html:377 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Journey" +msgstr "نموذج المرشح" + +#: templates/recruitment/candidate_detail.html:381 +#, fuzzy +#| msgid "Change Stage" +msgid "Current Stage" +msgstr "تغيير المرحلة" + +#: templates/recruitment/candidate_detail.html:385 +#, fuzzy +#| msgid "Last Updated:" +msgid "Latest status update:" +msgstr "آخر تحديث:" + +#: templates/recruitment/candidate_detail.html:389 +#, fuzzy +#| msgid "Financial & Timeline" +msgid "Historical Timeline" +msgstr "المالي والجدول الزمني" + +#: templates/recruitment/candidate_detail.html:397 +#, fuzzy +#| msgid "Application Stage" +msgid "Application Submitted" +msgstr "مرحلة التقديم" + +#: templates/recruitment/candidate_detail.html:458 +msgid "AI Generated Summary" +msgstr "ملخص تم إنشاؤه بواسطة الذكاء الاصطناعي" + +#: templates/recruitment/candidate_detail.html:468 +msgid "AI Analysis Report" +msgstr "" + +#: templates/recruitment/candidate_detail.html:474 +msgid "Match Score" +msgstr "" + +#: templates/recruitment/candidate_detail.html:487 +msgid "Category" +msgstr "" + +#: templates/recruitment/candidate_detail.html:490 +msgid "Job Fit Narrative" +msgstr "" + +#: templates/recruitment/candidate_detail.html:521 +#, fuzzy +#| msgid "Material Details" +msgid "Professional Details" +msgstr "تفاصيل المادة" + +#: templates/recruitment/candidate_detail.html:522 +msgid "Years of Experience:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:523 +msgid "Most Recent Job Title:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:524 +msgid "Experience Industry Match:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:529 +msgid "Soft Skills Score:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:534 +#, fuzzy +#| msgid "Sync Status" +msgid "Screening Status" +msgstr "حالة المزامنة" + +#: templates/recruitment/candidate_detail.html:536 +msgid "Minimum Requirements Met:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:544 +msgid "Screening Stage Rating:" +msgstr "" + +#: templates/recruitment/candidate_detail.html:601 +msgid "Resume is being parsed" +msgstr "" + +#: templates/recruitment/candidate_detail.html:602 +msgid "" +"Our AI is analyzing the candidate's resume to generate insights. This may " +"take a few moments." +msgstr "" + +#: templates/recruitment/candidate_detail.html:622 +msgid "Management Actions" +msgstr "إجراءات الإدارة" + +#: templates/recruitment/candidate_detail.html:625 +msgid "Edit Details" +msgstr "تعديل التفاصيل" + +#: templates/recruitment/candidate_detail.html:627 +msgid "Are you sure you want to delete this candidate?" +msgstr "هل أنت متأكد من رغبتك في حذف هذا المرشح؟" + +#: templates/recruitment/candidate_detail.html:628 +msgid "Delete Candidate" +msgstr "حذف المرشح" + +#: templates/recruitment/candidate_detail.html:637 +#, fuzzy +#| msgid "View API Response" +msgid "View Actual Resume" +msgstr "عرض استجابة واجهة برمجة التطبيقات" + +#: templates/recruitment/candidate_detail.html:641 +msgid "Download Resume" +msgstr "تحميل السيرة الذاتية" + +#: templates/recruitment/candidate_detail.html:646 +msgid "View Resume AI Overview" +msgstr "" + +#: templates/recruitment/candidate_detail.html:654 +msgid "Time to Hire: " +msgstr "" + +#: templates/recruitment/candidate_detail.html:679 +msgid "Unable to Parse Resume , click to retry" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:174 +#, fuzzy +#| msgid "Form Management" +msgid "Exam Management" +msgstr "إدارة النماذج" + +#: templates/recruitment/candidate_exam_view.html:177 +msgid "Candidates in Exam Stage:" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:183 +msgid "Export exam candidates to CSV" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:184 +#: templates/recruitment/candidate_hired_view.html:208 +#: templates/recruitment/candidate_interview_view.html:187 +#: templates/recruitment/candidate_offer_view.html:186 +#: templates/recruitment/candidate_screening_view.html:235 +msgid "Export CSV" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:197 +#: templates/recruitment/candidate_screening_view.html:312 +#, fuzzy +#| msgid "Candidates" +msgid "Candidate List" +msgstr "المرشحون" + +#: templates/recruitment/candidate_exam_view.html:219 +#, fuzzy +#| msgid "Interview Status" +msgid "Interview Stage" +msgstr "حالة المقابلة" + +#: templates/recruitment/candidate_exam_view.html:222 +msgid "Screening Stage" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:254 +#, fuzzy +#| msgid "Exam Status" +msgid "Exam Results" +msgstr "حالة الاختبار" + +#: templates/recruitment/candidate_exam_view.html:330 +msgid "No candidates are currently in the Exam stage for this job." +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:343 +msgid "Candidate Details & Exam Update" +msgstr "" + +#: templates/recruitment/candidate_exam_view.html:350 +#: templates/recruitment/candidate_screening_view.html:490 +#, fuzzy +#| msgid "No candidates found." +msgid "Loading candidate data..." +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/candidate_hired_view.html:192 +#, fuzzy +#| msgid "New Candidates" +msgid "Hired Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/candidate_hired_view.html:195 +msgid "Successfully Hired:" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:202 +msgid "Sync hired candidates to external sources" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:203 +#: templates/recruitment/candidate_hired_view.html:407 +#, fuzzy +#| msgid "Sources" +msgid "Sync to Sources" +msgstr "المصادر" + +#: templates/recruitment/candidate_hired_view.html:207 +msgid "Export hired candidates to CSV" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:219 +msgid "Congratulations!" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:220 +msgid "" +"These candidates have successfully completed the hiring process and joined " +"your team." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:234 +#, fuzzy +#| msgid "Open Positions" +msgid "Applied Position" +msgstr "المناصب المفتوحة" + +#: templates/recruitment/candidate_hired_view.html:300 +#, fuzzy +#| msgid "Candidates will appear here once they apply for this position." +msgid "No candidates have been hired for this position yet." +msgstr "سيظهر المرشحون هنا بمجرد تقديمهم لهذا المنصب." + +#: templates/recruitment/candidate_hired_view.html:313 +#, fuzzy +#| msgid "New Candidates" +msgid "Hired Candidate Details" +msgstr "المرشحون الجدد" + +#: templates/recruitment/candidate_hired_view.html:320 +#: templates/recruitment/candidate_interview_view.html:440 +#: templates/recruitment/candidate_interview_view.html:629 +#: templates/recruitment/candidate_offer_view.html:333 +msgid "Loading content..." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:333 +#, fuzzy +#| msgid "Sync Status" +msgid "Sync Results" +msgstr "حالة المزامنة" + +#: templates/recruitment/candidate_hired_view.html:340 +msgid "Syncing candidates..." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:369 +msgid "Syncing hired candidates..." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:370 +msgid "Please wait while we sync candidates to external sources." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:378 +msgid "Syncing..." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:402 +msgid "An unexpected error occurred during sync." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:419 +#, fuzzy +#| msgid "Summary" +msgid "Sync Summary" +msgstr "الملخص" + +#: templates/recruitment/candidate_hired_view.html:422 +#, fuzzy +#| msgid "Sources" +msgid "Total Sources:" +msgstr "المصادر" + +#: templates/recruitment/candidate_hired_view.html:425 +msgid "Successful:" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:428 +#, fuzzy +#| msgid "Failed" +msgid "Failed:" +msgstr "رسب" + +#: templates/recruitment/candidate_hired_view.html:431 +#, fuzzy +#| msgid "Candidates" +msgid "Candidates Synced:" +msgstr "المرشحون" + +#: templates/recruitment/candidate_hired_view.html:439 +#, fuzzy +#| msgid "Core Details" +msgid "Source Details" +msgstr "التفاصيل الأساسية" + +#: templates/recruitment/candidate_hired_view.html:457 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Candidates Processed:" +msgstr "ملفات المرشحين الشخصية" + +#: templates/recruitment/candidate_hired_view.html:461 +#: templates/recruitment/notification_detail.html:71 +#, fuzzy +#| msgid "Duration" +msgid "Duration:" +msgstr "المدة" + +#: templates/recruitment/candidate_hired_view.html:465 +#: templates/recruitment/notification_confirm_delete.html:21 +#, fuzzy +#| msgid "Error Message" +msgid "Message:" +msgstr "رسالة الخطأ" + +#: templates/recruitment/candidate_hired_view.html:494 +msgid "Sync task failed" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:503 +msgid "Failed to check sync status" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:510 +msgid "Sync timed out after 5 minutes" +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:521 +msgid "Sync in progress..." +msgstr "" + +#: templates/recruitment/candidate_hired_view.html:532 +#, fuzzy +#| msgid "Failed" +msgid "Sync Failed" +msgstr "رسب" + +#: templates/recruitment/candidate_interview_view.html:177 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Management" +msgstr "تاريخ المقابلة" + +#: templates/recruitment/candidate_interview_view.html:180 +msgid "Candidates in Interview Stage:" +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:186 +msgid "Export interview candidates to CSV" +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:215 +#, fuzzy +#| msgid "Offer" +msgid "To Offer" +msgstr "العرض" + +#: templates/recruitment/candidate_interview_view.html:218 +#, fuzzy +#| msgid "Exam" +msgid "To Exam" +msgstr "الاختبار" + +#: templates/recruitment/candidate_interview_view.html:232 +#, fuzzy +#| msgid "Interview" +msgid "Schedule Interviews" +msgstr "المقابلة" + +#: templates/recruitment/candidate_interview_view.html:240 +#, fuzzy +#| msgid "Participant Video" +msgid "Manage Participants" +msgstr "فيديو المشارك" + +#: templates/recruitment/candidate_interview_view.html:265 +#, fuzzy +#| msgid "Meeting ID" +msgid "Meeting Date" +msgstr "معرف الاجتماع" + +#: templates/recruitment/candidate_interview_view.html:267 +#, fuzzy +#| msgid "Meeting Details" +msgid "Meeting Status" +msgstr "تفاصيل الاجتماع" + +#: templates/recruitment/candidate_interview_view.html:268 +#, fuzzy +#| msgid "Interview Date" +msgid "Interview Result" +msgstr "تاريخ المقابلة" + +#: templates/recruitment/candidate_interview_view.html:300 +msgid "Minutes" +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:421 +msgid "No candidates are currently in the Interview stage for this job." +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:433 +#: templates/recruitment/candidate_interview_view.html:633 +#: templates/recruitment/candidate_offer_view.html:326 +msgid "Candidate Details / Bulk Action Form" +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:453 +msgid "Manage all participants" +msgstr "" + +#: templates/recruitment/candidate_interview_view.html:476 +#: templates/recruitment/candidate_interview_view.html:501 +msgid "Users" +msgstr "المستخدمون" + +#: templates/recruitment/candidate_interview_view.html:530 +msgid "Loading email form..." +msgstr "" + +#: templates/recruitment/candidate_list.html:193 +msgid "Candidate Profiles" +msgstr "ملفات المرشحين الشخصية" + +#: templates/recruitment/candidate_list.html:197 +msgid "Add New Candidate" +msgstr "إضافة مرشح جديد" + +#: templates/recruitment/candidate_list.html:221 +msgid "Filter by Job" +msgstr "" + +#: templates/recruitment/candidate_list.html:274 +msgid "Major" +msgstr "" + +#: templates/recruitment/candidate_list.html:277 +#, fuzzy +#| msgid "Created at" +msgid "created At" +msgstr "تم الإنشاء في" + +#: templates/recruitment/candidate_list.html:401 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidate profiles found" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/candidate_list.html:402 +#, fuzzy +#| msgid "Start by adding a new profile or adjusting your search filters." +msgid "Create your first candidate profile or adjust your filters." +msgstr "ابدأ بإضافة ملف شخصي جديد أو تعديل عوامل التصفية الخاصة بك." + +#: templates/recruitment/candidate_list.html:405 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add Candidate" +msgstr "إضافة مرشح جديد" + +#: templates/recruitment/candidate_offer_view.html:176 +#, fuzzy +#| msgid "Form Management" +msgid "Offer Management" +msgstr "إدارة النماذج" + +#: templates/recruitment/candidate_offer_view.html:179 +msgid "Candidates in Offer Stage:" +msgstr "" + +#: templates/recruitment/candidate_offer_view.html:185 +msgid "Export offer candidates to CSV" +msgstr "" + +#: templates/recruitment/candidate_offer_view.html:213 +msgid "To Hired" +msgstr "" + +#: templates/recruitment/candidate_offer_view.html:216 +#, fuzzy +#| msgid "Rejected" +msgid "To Rejected" +msgstr "رفض" + +#: templates/recruitment/candidate_offer_view.html:313 +msgid "No candidates are currently in the Offer stage for this job." +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:224 +#, fuzzy +#| msgid "Application" +msgid "Applicant Screening" +msgstr "التقديم" + +#: templates/recruitment/candidate_screening_view.html:227 +msgid "Job:" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:234 +msgid "Export screening candidates to CSV" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:249 +msgid "AI Scoring & Top Candidate Filter" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:257 +msgid "Min AI Score" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:266 +msgid "Min Years Exp" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:278 +msgid "Any Rating" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:296 +#, fuzzy +#| msgid "New Candidates" +msgid "Top N Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/candidate_screening_view.html:304 +#, fuzzy +#| msgid "Update Material" +msgid "Update Filters" +msgstr "تحديث المادة" + +#: templates/recruitment/candidate_screening_view.html:333 +#, fuzzy +#| msgid "Exam Status" +msgid "Exam Stage" +msgstr "حالة الاختبار" + +#: templates/recruitment/candidate_screening_view.html:367 +#, fuzzy +#| msgid "Contact & Job" +msgid "Contact Info" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/candidate_screening_view.html:373 +msgid "Is Qualified?" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:376 +msgid "Professional Category" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:379 +msgid "Top 3 Skills" +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:422 +msgid "AI scoring.." +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:469 +msgid "No candidates match the current stage and filter criteria." +msgstr "" + +#: templates/recruitment/candidate_screening_view.html:483 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Candidate Criteria Review" +msgstr "ملفات المرشحين الشخصية" + +#: templates/recruitment/candidate_update.html:92 +msgid "Update Candidate:" +msgstr "تحديث المرشح:" + +#: templates/recruitment/candidate_update.html:94 +msgid "Edit candidate information and details" +msgstr "تعديل معلومات المرشح وتفاصيله" + +#: templates/recruitment/candidate_update.html:102 +msgid "View Candidate" +msgstr "عرض المرشح" + +#: templates/recruitment/candidate_update.html:116 +msgid "Candidate Form" +msgstr "نموذج المرشح" + +#: templates/recruitment/candidate_update.html:135 +msgid "Update Candidate" +msgstr "تحديث المرشح" + +#: templates/recruitment/dashboard.html:4 +#, fuzzy +#| msgid "Dashboard" +msgid "Recruitment Dashboard" +msgstr "لوحة التحكم" + +#: templates/recruitment/dashboard.html:123 +msgid "Recruitment Analytics" +msgstr "" + +#: templates/recruitment/dashboard.html:133 +msgid "Data Scope: " +msgstr "" + +#: templates/recruitment/dashboard.html:135 +msgid "Data Scope: All Jobs" +msgstr "" + +#: templates/recruitment/dashboard.html:140 +#, fuzzy +#| msgid "Edit Job" +msgid "Filter Job:" +msgstr "تعديل الوظيفة" + +#: templates/recruitment/dashboard.html:142 +msgid "All Jobs (Default View)" +msgstr "" + +#: templates/recruitment/dashboard.html:170 +msgid "Daily Candidate Applications Trend" +msgstr "" + +#: templates/recruitment/dashboard.html:184 +#, fuzzy +#| msgid "Application Form" +msgid "Top 5 Application Volume" +msgstr "نموذج التقديم" + +#: templates/recruitment/dashboard.html:200 +msgid "Pipeline Funnel: " +msgstr "" + +#: templates/recruitment/dashboard.html:202 +msgid "Total Pipeline Funnel (All Jobs)" +msgstr "" + +#: templates/recruitment/dashboard.html:216 +msgid "Time-to-Hire Target Check" +msgstr "" + +#: templates/recruitment/dashboard.html:231 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidates From Each Sources" +msgstr "نموذج المرشح" + +#: templates/recruitment/dashboard.html:278 +msgid "Top 5 Most Applied Jobs" +msgstr "" + +#: templates/recruitment/dashboard.html:286 +#, fuzzy +#| msgid "Applications" +msgid "Total Applications" +msgstr "التقديمات" + +#: templates/recruitment/dashboard.html:338 +#, fuzzy +#| msgid "Candidate Form" +msgid "Candidate Count" +msgstr "نموذج المرشح" + +#: templates/recruitment/dashboard.html:406 +#, fuzzy +#| msgid "Create Job" +msgid "Current Job" +msgstr "إنشاء وظيفة" + +#: templates/recruitment/dashboard.html:431 +msgid "Daily Applications (Last 30 Days)" +msgstr "" + +#: templates/recruitment/dashboard.html:446 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:598 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:639 +#, fuzzy +#| msgid "End Date" +msgid "Date" +msgstr "تاريخ الانتهاء" + +#: templates/recruitment/dashboard.html:451 +msgid "New Candidates" +msgstr "المرشحون الجدد" + +#: templates/recruitment/notification_confirm_all_read.html:4 +msgid "Mark All as Read" +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:22 +msgid "What this will do" +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:25 +#, python-format +msgid "" +"\n" +" This will mark %(count)s unread " +"notification as read.\n" +" " +msgid_plural "" +"\n" +" This will mark all %(count)s unread " +"notifications as read.\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: templates/recruitment/notification_confirm_all_read.html:32 +msgid "" +"You can still view all notifications in your notification list, but they " +"won't appear as unread." +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:38 +msgid "All caught up!" +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:41 +msgid "You don't have any unread notifications to mark as read." +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:50 +msgid "Yes, Mark All as Read" +msgstr "" + +#: templates/recruitment/notification_confirm_all_read.html:58 +#: templates/recruitment/notification_detail.html:18 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Notifications" +msgstr "العودة إلى الاجتماعات" + +#: templates/recruitment/notification_confirm_delete.html:4 +#, fuzzy +#| msgid "Delete Meeting" +msgid "Delete Notification" +msgstr "حذف الاجتماع" + +#: templates/recruitment/notification_confirm_delete.html:20 +msgid "Notification Preview" +msgstr "" + +#: templates/recruitment/notification_confirm_delete.html:30 +#, fuzzy +#| msgid "Delete" +msgid "Yes, Delete" +msgstr "حذف" + +#: templates/recruitment/notification_detail.html:4 +#: templates/recruitment/notification_detail.html:12 +#, fuzzy +#| msgid "Meeting Details" +msgid "Notification Details" +msgstr "تفاصيل الاجتماع" + +#: templates/recruitment/notification_detail.html:14 +msgid "View notification details and manage your preferences" +msgstr "" + +#: templates/recruitment/notification_detail.html:47 +#: templates/recruitment/notification_detail.html:132 +msgid "Mark as Read" +msgstr "" + +#: templates/recruitment/notification_detail.html:51 +#: templates/recruitment/notification_detail.html:136 +msgid "Mark as Unread" +msgstr "" + +#: templates/recruitment/notification_detail.html:65 +msgid "Topic:" +msgstr "الموضوع:" + +#: templates/recruitment/notification_detail.html:68 +#, fuzzy +#| msgid "Start Time" +msgid "Start Time:" +msgstr "وقت البدء" + +#: templates/recruitment/notification_detail.html:75 +#, fuzzy +#| msgid "Join Meeting" +msgid "View Meeting" +msgstr "الانضمام إلى الاجتماع" + +#: templates/recruitment/notification_detail.html:84 +#: templates/recruitment/notification_detail.html:175 +#, fuzzy +#| msgid "Scheduled" +msgid "Scheduled For" +msgstr "مجدول" + +#: templates/recruitment/notification_detail.html:95 +#: templates/recruitment/notification_detail.html:182 +msgid "Delivery Attempts" +msgstr "" + +#: templates/recruitment/notification_detail.html:98 +#, python-format +msgid "" +"\n" +" This notification has been attempted " +"%(count)s time.\n" +" " +msgid_plural "" +"\n" +" This notification has been attempted " +"%(count)s times.\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: templates/recruitment/notification_detail.html:110 +#, fuzzy +#| msgid "Error" +msgid "Last Error" +msgstr "خطأ" + +#: templates/recruitment/notification_detail.html:150 +#, fuzzy +#| msgid "Join Information" +msgid "Information" +msgstr "معلومات الانضمام" + +#: templates/recruitment/notification_list.html:15 +#, python-format +msgid "" +"\n" +" %(count)s notification\n" +" " +msgid_plural "" +"\n" +" %(count)s notifications\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: templates/recruitment/notification_list.html:26 +msgid "Mark All Read" +msgstr "" + +#: templates/recruitment/notification_list.html:39 +#, fuzzy +#| msgid "Status" +msgid "All Status" +msgstr "الحالة" + +#: templates/recruitment/notification_list.html:40 +#: templates/recruitment/notification_list.html:82 +msgid "Unread" +msgstr "" + +#: templates/recruitment/notification_list.html:48 +msgid "All Types" +msgstr "" + +#: templates/recruitment/notification_list.html:57 +msgid "Filter" +msgstr "تصفية" + +#: templates/recruitment/notification_list.html:74 +msgid "Total Notifications" +msgstr "" + +#: templates/recruitment/notification_list.html:90 +msgid "Email Notifications" +msgstr "" + +#: templates/recruitment/notification_list.html:122 +#, fuzzy +#| msgid "Create Zoom Meeting" +msgid "Related to meeting:" +msgstr "إنشاء اجتماع Zoom" + +#: templates/recruitment/notification_list.html:130 +msgid "Mark as read" +msgstr "" + +#: templates/recruitment/notification_list.html:136 +msgid "Mark as unread" +msgstr "" + +#: templates/recruitment/notification_list.html:142 +#, fuzzy +#| msgid "Delete Candidate" +msgid "Delete notification" +msgstr "حذف المرشح" + +#: templates/recruitment/notification_list.html:155 +msgid "Notifications pagination" +msgstr "" + +#: templates/recruitment/notification_list.html:190 +#, fuzzy +#| msgid "No candidates found." +msgid "No notifications found" +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/notification_list.html:193 +msgid "Try adjusting your filters to see more notifications." +msgstr "" + +#: templates/recruitment/notification_list.html:195 +#, fuzzy +#| msgid "You haven't created any form templates yet." +msgid "You don't have any notifications yet." +msgstr "لم تقم بإنشاء أي قوالب نماذج بعد." + +#: templates/recruitment/partials/_candidate_table.html:10 +msgid "Name / Contact" +msgstr "" + +#: templates/recruitment/partials/_candidate_table.html:64 +msgid "View Details and Score Breakdown" +msgstr "" + +#: templates/recruitment/partials/_candidate_table.html:75 +#, fuzzy +#| msgid "Create Candidate" +msgid "Mark as Potential Candidate" +msgstr "إنشاء مرشح" + +#: templates/recruitment/partials/_candidate_table.html:83 +msgid "Move to Next Stage" +msgstr "" + +#: templates/recruitment/partials/_candidate_table.html:92 +msgid "Move to" +msgstr "" + +#: templates/recruitment/partials/_candidate_table.html:102 +#, fuzzy +#| msgid "Exam Status" +msgid "Update Exam Status" +msgstr "حالة الاختبار" + +#: templates/recruitment/partials/_candidate_table.html:120 +#, fuzzy +#| msgid "No candidates found." +msgid "No candidates found in this list." +msgstr "لم يتم العثور على مرشحين." + +#: templates/recruitment/partials/_candidate_table.html:122 +msgid "" +"Adjust your 'Top N' filter in the controls above or check the All Applicants " +"list." +msgstr "" + +#: templates/recruitment/partials/_guage_chart.html:36 +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Days" +msgstr "" + +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Target:" +msgstr "" + +#: templates/recruitment/partials/_guage_chart.html:50 +msgid "Max Scale:" +msgstr "" + +#: templates/recruitment/partials/ai_overview_breadcromb.html:25 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:9 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_icon.html:11 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_logo.html:9 +msgid "Home" +msgstr "الرئيسية" + +#: templates/recruitment/partials/ai_overview_breadcromb.html:113 +#, fuzzy +#| msgid "Job Overview" +msgid "AI Overview" +msgstr "نظرة عامة على الوظيفة" + +#: templates/recruitment/partials/stats_cards.html:10 +#, fuzzy +#| msgid "Contact & Job" +msgid "Total Jobs" +msgstr "الاتصال والوظيفة" + +#: templates/recruitment/partials/stats_cards.html:13 +msgid "All Active & Drafted Positions (Global)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:19 +msgid "Active Jobs" +msgstr "الوظائف النشطة" + +#: templates/recruitment/partials/stats_cards.html:22 +msgid "Currently Open Requisitions (Scoped)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:31 +msgid "Total Profiles in Current Scope" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:40 +msgid "Total Slots to be Filled (Scoped)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:46 +#, fuzzy +#| msgid "Participant Video" +msgid "Total Participants" +msgstr "فيديو المشارك" + +#: templates/recruitment/partials/stats_cards.html:49 +msgid "Total Recruiters/Interviewers (Global)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:55 +#, fuzzy +#| msgid "LinkedIn Connected" +msgid "LinkedIn Posts" +msgstr "LinkedIn متصل" + +#: templates/recruitment/partials/stats_cards.html:58 +msgid "Total Job Posts Sent to LinkedIn (Global)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:63 +msgid "New Apps (7 Days)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:66 +msgid "Incoming applications last week" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:71 +msgid "Avg. Apps per Job" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:74 +msgid "Average Applications per Job (Scoped)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:81 +#, fuzzy +#| msgid "Timezone" +msgid "Time-to-Hire" +msgstr "المنطقة الزمنية" + +#: templates/recruitment/partials/stats_cards.html:84 +msgid "Avg. Days (Application to Hired)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:89 +msgid "Avg. Match Score" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:92 +msgid "Average AI Score (Current Scope)" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:100 +#, python-format +msgid "Score ≥ 75%% Profiles" +msgstr "" + +#: templates/recruitment/partials/stats_cards.html:105 +#, fuzzy +#| msgid "Meetings" +msgid "Meetings This Week" +msgstr "الاجتماعات" + +#: templates/recruitment/partials/stats_cards.html:108 +msgid "Scheduled Interviews (Current Week)" +msgstr "" + +#: templates/recruitment/schedule_meeting_form.html:22 +msgid "" +"This candidate has upcoming interviews. You are updating an existing " +"schedule." +msgstr "" + +#: templates/recruitment/schedule_meeting_form.html:27 +#, fuzzy +#| msgid "Candidates" +msgid "Back to Candidates" +msgstr "المرشحون" + +#: templates/recruitment/schedule_meeting_form.html:49 +msgid "" +"Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left " +"empty." +msgstr "" + +#: templates/recruitment/schedule_meeting_form.html:66 +msgid "Please select a date and time for the interview." +msgstr "" + +#: templates/recruitment/training_create.html:107 +msgid "Create New Training Material" +msgstr "إنشاء مادة تدريبية جديدة" + +#: templates/recruitment/training_create.html:109 +msgid "Upload a new document or guide for your team." +msgstr "رفع مستند أو دليل جديد لفريقك." + +#: templates/recruitment/training_create.html:125 +#: templates/recruitment/training_update.html:131 +msgid "Material Details" +msgstr "تفاصيل المادة" + +#: templates/recruitment/training_list.html:132 +msgid "Add New Material" +msgstr "إضافة مادة جديدة" + +#: templates/recruitment/training_list.html:141 +msgid "Search by Title or Creator" +msgstr "" + +#: templates/recruitment/training_list.html:170 +#: templates/recruitment/training_list.html:205 +msgid "Created By" +msgstr "أنشأ بواسطة" + +#: templates/recruitment/training_list.html:171 +#, fuzzy +#| msgid "Created" +msgid "Created On" +msgstr "تم الإنشاء" + +#: templates/recruitment/training_list.html:274 +#, fuzzy +#| msgid "No training materials found." +msgid "No training materials found" +msgstr "لم يتم العثور على مواد تدريبية." + +#: templates/recruitment/training_list.html:275 +msgid "It looks like there are no materials yet. Start by adding one!" +msgstr "يبدو أنه لا توجد مواد بعد. ابدأ بإضافة واحدة!" + +#: templates/recruitment/training_list.html:278 +msgid "Create Your First Material" +msgstr "إنشاء أول مادة لك" + +#: templates/recruitment/training_update.html:107 +msgid "Update Training Material:" +msgstr "تحديث المادة التدريبية:" + +#: templates/recruitment/training_update.html:109 +msgid "Edit the details of this training document or guide." +msgstr "تعديل تفاصيل هذا المستند التدريبي أو الدليل." + +#: templates/recruitment/training_update.html:117 +msgid "View Material" +msgstr "عرض المادة" + +#: templates/recruitment/training_update.html:180 +msgid "Update Material" +msgstr "تحديث المادة" + +#: templates/recruitment/training_update.html:182 +msgid "Are you sure you want to delete this material?" +msgstr "هل أنت متأكد من رغبتك في حذف هذه المادة؟" + +#: templates/unfold/components/table.html:43 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/components/table.html:89 +msgid "No data" +msgstr "لا توجد بيانات" + +#: templates/user/admin_settings.html:6 +#, fuzzy +#| msgid "Settings" +msgid "Admin Settings" +msgstr "الإعدادات" + +#: templates/user/admin_settings.html:149 +msgid "Admin Settings Dashboard" +msgstr "" + +#: templates/user/admin_settings.html:159 +msgid "Staff User List" +msgstr "" + +#: templates/user/admin_settings.html:163 +#, fuzzy +#| msgid "Create New Form" +msgid "Create New User" +msgstr "إنشاء نموذج جديد" + +#: templates/user/admin_settings.html:177 +#, fuzzy +#| msgid "First Name" +msgid "First Join" +msgstr "الاسم الأول" + +#: templates/user/admin_settings.html:178 templates/user/profile.html:178 +msgid "Last Login" +msgstr "" + +#: templates/user/admin_settings.html:231 +msgid "Deactivate User" +msgstr "" + +#: templates/user/admin_settings.html:238 +#, fuzzy +#| msgid "Active Jobs" +msgid "Activate User" +msgstr "الوظائف النشطة" + +#: templates/user/admin_settings.html:239 +#, fuzzy +#| msgid "Active" +msgid "Activate" +msgstr "نشط" + +#: templates/user/admin_settings.html:248 +#, fuzzy +#| msgid "No training materials found." +msgid "No staff users found." +msgstr "لم يتم العثور على مواد تدريبية." + +#: templates/user/create_staff.html:6 templates/user/create_staff.html:37 +#: templates/user/create_staff.html:69 +#, fuzzy +#| msgid "Create Material" +msgid "Create Staff User" +msgstr "إنشاء مادة" + +#: templates/user/create_staff.html:76 +#, fuzzy +#| msgid "Back to Meetings" +msgid "Back to Settings" +msgstr "العودة إلى الاجتماعات" + +#: templates/user/profile.html:5 +#, fuzzy +#| msgid "Profile" +msgid "User Profile" +msgstr "الملف الشخصي" + +#: templates/user/profile.html:142 +msgid "Manage email addresses" +msgstr "" + +#: templates/user/profile.html:157 +msgid "Security" +msgstr "" + +#: templates/user/profile.html:164 +#, fuzzy +#| msgid "Candidate Profiles" +msgid "Change Profile Image" +msgstr "ملفات المرشحين الشخصية" + +#: templates/user/profile.html:170 +#, fuzzy +#| msgid "Sync Status" +msgid "Account Status" +msgstr "حالة المزامنة" + +#: templates/user/profile.html:173 +msgid "Username" +msgstr "اسم المستخدم" + +#: templates/user/profile.html:185 +msgid "Date Joined" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/_termui_impl.py:608 +#, python-brace-format +msgid "{editor}: Editing failed" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/_termui_impl.py:612 +#, python-brace-format +msgid "{editor}: Editing failed: {e}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1104 +#: venv/lib/python3.13/site-packages/click/core.py:1141 +#, python-brace-format +msgid "{text} {deprecated_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1160 +#: venv/lib/python3.13/site-packages/typer/core.py:633 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:96 +#, fuzzy +#| msgid "Actions" +msgid "Options" +msgstr "الإجراءات" + +#: venv/lib/python3.13/site-packages/click/core.py:1222 +#, python-brace-format +msgid "Got unexpected extra argument ({args})" +msgid_plural "Got unexpected extra arguments ({args})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/core.py:1241 +msgid "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1425 +#: venv/lib/python3.13/site-packages/typer/core.py:249 +msgid "Aborted!" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1799 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:97 +msgid "Commands" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1830 +msgid "Missing command." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:1908 +msgid "No such command {name!r}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2332 +msgid "Value must be an iterable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2355 +#, python-brace-format +msgid "Takes {nargs} values but 1 was given." +msgid_plural "Takes {nargs} values but {len} were given." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/core.py:2505 +msgid "" +"DeprecationWarning: The {param_type} {name!r} is deprecated.{extra_message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2956 +#: venv/lib/python3.13/site-packages/typer/core.py:553 +#, python-brace-format +msgid "env var: {var}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:2959 +#: venv/lib/python3.13/site-packages/typer/core.py:366 +#: venv/lib/python3.13/site-packages/typer/core.py:574 +#, python-brace-format +msgid "default: {default}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/core.py:3023 +#: venv/lib/python3.13/site-packages/typer/core.py:114 +msgid "(dynamic)" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:465 +#, python-format +msgid "%(prog)s, version %(version)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:522 +msgid "Show the version and exit." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/decorators.py:548 +msgid "Show this message and exit." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:50 +#: venv/lib/python3.13/site-packages/click/exceptions.py:89 +#, fuzzy, python-brace-format +#| msgid "Error Message" +msgid "Error: {message}" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:81 +#, python-brace-format +msgid "Try '{command} {option}' for help." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:130 +#, python-brace-format +msgid "Invalid value: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:132 +#, python-brace-format +msgid "Invalid value for {param_hint}: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:190 +msgid "Missing argument" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:192 +#, fuzzy +#| msgid "Meeting Information" +msgid "Missing option" +msgstr "معلومات الاجتماع" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:194 +msgid "Missing parameter" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:196 +#, python-brace-format +msgid "Missing {param_type}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:203 +#, python-brace-format +msgid "Missing parameter: {param_name}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:223 +#, python-brace-format +msgid "No such option: {name}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:235 +#, python-brace-format +msgid "Did you mean {possibility}?" +msgid_plural "(Possible options: {possibilities})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:282 +msgid "unknown error" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/exceptions.py:289 +msgid "Could not open file {filename!r}: {message}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/formatting.py:156 +#, fuzzy +#| msgid "Stage:" +msgid "Usage:" +msgstr "المرحلة:" + +#: venv/lib/python3.13/site-packages/click/parser.py:199 +msgid "Argument {name!r} takes {nargs} values." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/parser.py:381 +msgid "Option {name!r} does not take a value." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/parser.py:444 +msgid "Option {name!r} requires an argument." +msgid_plural "Option {name!r} requires {nargs} arguments." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/shell_completion.py:332 +msgid "Shell completion is not supported for Bash versions older than 4.4." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/shell_completion.py:339 +msgid "Couldn't detect Bash version, shell completion is not supported." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:162 +#, fuzzy +#| msgid "Meeting Information" +msgid "Repeat for confirmation" +msgstr "معلومات الاجتماع" + +#: venv/lib/python3.13/site-packages/click/termui.py:178 +msgid "Error: The value you entered was invalid." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:180 +#, fuzzy, python-brace-format +#| msgid "Error Message" +msgid "Error: {e.message}" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/click/termui.py:191 +msgid "Error: The two entered values do not match." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:247 +msgid "Error: invalid input" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/termui.py:866 +msgid "Press any key to continue..." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:332 +#, python-brace-format +msgid "" +"Choose from:\n" +"\t{choices}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:369 +msgid "{value!r} is not {choice}." +msgid_plural "{value!r} is not one of {choices}." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/types.py:460 +msgid "{value!r} does not match the format {format}." +msgid_plural "{value!r} does not match the formats {formats}." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/click/types.py:482 +msgid "{value!r} is not a valid {number_type}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:538 +#, python-brace-format +msgid "{value} is not in the range {range}." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:719 +msgid "{value!r} is not a valid boolean. Recognized values: {states}" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:747 +msgid "{value!r} is not a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:937 +#, fuzzy +#| msgid "Profile" +msgid "file" +msgstr "الملف الشخصي" + +#: venv/lib/python3.13/site-packages/click/types.py:939 +msgid "directory" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:941 +msgid "path" +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:988 +msgid "{name} {filename!r} does not exist." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:997 +msgid "{name} {filename!r} is a file." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1005 +msgid "{name} {filename!r} is a directory." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1014 +msgid "{name} {filename!r} is not readable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1023 +msgid "{name} {filename!r} is not writable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1032 +msgid "{name} {filename!r} is not executable." +msgstr "" + +#: venv/lib/python3.13/site-packages/click/types.py:1099 +#, python-brace-format +msgid "{len_type} values are required, but {len_value} was given." +msgid_plural "{len_type} values are required, but {len_value} were given." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:240 +msgid "RoW" +msgstr "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:240 +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:243 +msgid "GLO" +msgstr "" + +#: venv/lib/python3.13/site-packages/constructive_geometries/geomatcher.py:243 +msgid "RoE" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/messages/apps.py:16 +#, fuzzy +#| msgid "Error Message" +msgid "Messages" +msgstr "رسالة الخطأ" + +#: venv/lib/python3.13/site-packages/django/contrib/sitemaps/apps.py:8 +msgid "Site Maps" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/contrib/syndication/apps.py:7 +#, fuzzy +#| msgid "Application" +msgid "Syndication" +msgstr "التقديم" + +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: venv/lib/python3.13/site-packages/django/core/paginator.py:30 +msgid "…" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:32 +msgid "That page number is not an integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:33 +msgid "That page number is less than 1" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/paginator.py:34 +msgid "That page contains no results" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:22 +msgid "Enter a valid value." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:70 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a valid domain name." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:153 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:775 +msgid "Enter a valid URL." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:200 +msgid "Enter a valid integer." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:211 +msgid "Enter a valid email address." +msgstr "" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: venv/lib/python3.13/site-packages/django/core/validators.py:289 +msgid "" +"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:297 +msgid "" +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:309 +#: venv/lib/python3.13/site-packages/django/core/validators.py:318 +#: venv/lib/python3.13/site-packages/django/core/validators.py:332 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2220 +#, python-format +msgid "Enter a valid %(protocol)s address." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:311 +msgid "IPv4" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:320 +#: venv/lib/python3.13/site-packages/django/utils/ipv6.py:43 +msgid "IPv6" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:334 +msgid "IPv4 or IPv6" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:375 +msgid "Enter only digits separated by commas." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:381 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:416 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:425 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:434 +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:441 +#, python-format +msgid "" +"Ensure this value is a multiple of step size %(limit_value)s, starting from " +"%(offset)s, e.g. %(offset)s, %(valid_value1)s, %(valid_value2)s, and so on." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:473 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:491 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:514 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:366 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:405 +#, fuzzy +#| msgid "Enter phone number" +msgid "Enter a number." +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:516 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:521 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:526 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:597 +#, python-format +msgid "" +"File extension “%(extension)s” is not allowed. Allowed extensions are: " +"%(allowed_extensions)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/core/validators.py:659 +msgid "Null characters are not allowed." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/base.py:1600 +#: venv/lib/python3.13/site-packages/django/forms/models.py:908 +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:108 +msgid "and" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/base.py:1602 +#, python-format +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/constraints.py:22 +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:134 +#, python-format +msgid "Value %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:135 +msgid "This field cannot be null." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:136 +msgid "This field cannot be blank." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:137 +#, python-format +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:141 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:180 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1162 +#, python-format +msgid "“%(value)s” value must be either True or False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1163 +#, python-format +msgid "“%(value)s” value must be either True, False, or None." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1165 +msgid "Boolean (Either True or False)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1215 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1217 +msgid "String (unlimited)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1326 +#, fuzzy +#| msgid "Comma-separated list of trusted IP addresses" +msgid "Comma-separated integers" +msgstr "قائمة عناوين IP الموثوقة مفصولة بفواصل" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1427 +#, python-format +msgid "" +"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1431 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1566 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1435 +msgid "Date (without time)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1562 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD " +"HH:MM[:ss[.uuuuuu]][TZ] format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1570 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1575 +msgid "Date (with time)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1702 +#, python-format +msgid "“%(value)s” value must be a decimal number." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1704 +msgid "Decimal number" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1864 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in [DD] " +"[[HH:]MM:]ss[.uuuuuu] format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1920 +#, fuzzy +#| msgid "IP Address" +msgid "Email address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:1945 +#, fuzzy +#| msgid "File" +msgid "File path" +msgstr "ملف" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2023 +#, python-format +msgid "“%(value)s” value must be a float." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2025 +#, fuzzy +#| msgid "Enter phone number" +msgid "Floating point number" +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2065 +#, python-format +msgid "“%(value)s” value must be an integer." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2067 +#, fuzzy +#| msgid "Interview" +msgid "Integer" +msgstr "المقابلة" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2163 +msgid "Big (8 byte) integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2180 +msgid "Small integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2188 +#, fuzzy +#| msgid "IP Address" +msgid "IPv4 address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2219 +#, fuzzy +#| msgid "IP Address" +msgid "IP address" +msgstr "عنوان IP" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2310 +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2311 +#, python-format +msgid "“%(value)s” value must be either None, True or False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2313 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2364 +msgid "Positive big integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2379 +msgid "Positive integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2394 +msgid "Positive small integer" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2410 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2446 +msgid "Text" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2526 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2530 +#, python-format +msgid "" +"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2534 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:263 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:279 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:599 +#: venv/lib/python3.13/site-packages/unfold/widgets.py:644 +#, fuzzy +#| msgid "Timezone" +msgid "Time" +msgstr "المنطقة الزمنية" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2642 +msgid "URL" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2666 +msgid "Raw binary data" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2731 +#, python-format +msgid "“%(value)s” is not a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/__init__.py:2733 +msgid "Universally unique identifier" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/files.py:420 +msgid "Image" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/json.py:24 +msgid "A JSON object" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/json.py:26 +msgid "Value must be valid JSON." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:979 +#, python-format +msgid "%(model)s instance with %(field)s %(value)r is not a valid choice." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:982 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1276 +msgid "One-to-one relationship" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1333 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1335 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/db/models/fields/related.py:1383 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: venv/lib/python3.13/site-packages/django/forms/boundfield.py:185 +msgid ":?.!" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:95 +msgid "This field is required." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:315 +#, fuzzy +#| msgid "Enter phone number" +msgid "Enter a whole number." +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:486 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1267 +#, fuzzy +#| msgid "Interview Date" +msgid "Enter a valid date." +msgstr "تاريخ المقابلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:509 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1268 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a valid time." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:536 +msgid "Enter a valid date/time." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:570 +#, fuzzy +#| msgid "Internal Information" +msgid "Enter a valid duration." +msgstr "المعلومات الداخلية" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:571 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:640 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:641 +msgid "No file was submitted." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:642 +msgid "The submitted file is empty." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:644 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:649 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:717 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:889 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:975 +#: venv/lib/python3.13/site-packages/django/forms/models.py:1592 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:977 +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1096 +#: venv/lib/python3.13/site-packages/django/forms/models.py:1590 +#, fuzzy +#| msgid "Enter last name" +msgid "Enter a list of values." +msgstr "أدخل اسم العائلة" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1097 +#, fuzzy +#| msgid "Enter template name" +msgid "Enter a complete value." +msgstr "أدخل اسم القالب" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1339 +msgid "Enter a valid UUID." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/fields.py:1369 +msgid "Enter a valid JSON." +msgstr "" + +#. Translators: This is the default suffix added to form field labels +#: venv/lib/python3.13/site-packages/django/forms/forms.py:97 +msgid ":" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/forms.py:239 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:61 +#, python-format +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:65 +#, python-format +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:70 +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:484 +#: venv/lib/python3.13/site-packages/django/forms/formsets.py:491 +msgid "Order" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:901 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:906 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:913 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:922 +msgid "Please correct the duplicate values below." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1359 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1450 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/models.py:1594 +#, python-format +msgid "“%(pk)s” is not a valid value." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/utils.py:229 +#, python-format +msgid "" +"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:528 +msgid "Currently" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:529 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:63 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:42 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:23 +#, fuzzy +#| msgid "Change Stage" +msgid "Change" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/django/forms/widgets.py:866 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "Unknown" +msgstr "" + +#. Translators: Please do not add spaces around commas. +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:873 +msgid "yes,no,maybe" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:903 +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:920 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:922 +#, python-format +msgid "%s KB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:924 +#, python-format +msgid "%s MB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:926 +#, python-format +msgid "%s GB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:928 +#, python-format +msgid "%s TB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/template/defaultfilters.py:930 +#, python-format +msgid "%s PB" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:74 +msgid "p.m." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:75 +msgid "a.m." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:80 +msgid "PM" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:81 +msgid "AM" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:153 +msgid "midnight" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dateformat.py:155 +msgid "noon" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:7 +msgid "Monday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:8 +msgid "Tuesday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:9 +msgid "Wednesday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:10 +msgid "Thursday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:11 +msgid "Friday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:12 +msgid "Saturday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:13 +msgid "Sunday" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:16 +msgid "Mon" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:17 +msgid "Tue" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:18 +msgid "Wed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:19 +msgid "Thu" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:20 +msgid "Fri" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:21 +#, fuzzy +#| msgid "Status" +msgid "Sat" +msgstr "الحالة" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:22 +msgid "Sun" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:25 +msgid "January" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:26 +msgid "February" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:27 +#, fuzzy +#| msgid "Search" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:28 +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:29 +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:30 +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:31 +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:32 +msgid "August" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:33 +msgid "September" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:34 +msgid "October" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:35 +msgid "November" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:36 +msgid "December" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:39 +msgid "jan" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:40 +msgid "feb" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:41 +#, fuzzy +#| msgid "Summary" +msgid "mar" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:42 +msgid "apr" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:43 +#, fuzzy +#| msgid "Summary" +msgid "may" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:44 +msgid "jun" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:45 +msgid "jul" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:46 +#, fuzzy +#| msgid "ago" +msgid "aug" +msgstr "منذ" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:47 +msgid "sep" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:48 +msgid "oct" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:49 +msgid "nov" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:50 +msgid "dec" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:53 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:54 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:55 +#, fuzzy +#| msgid "Search" +msgctxt "abbrev. month" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:56 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:57 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:58 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:59 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:60 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:61 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:62 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:63 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:64 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:67 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:68 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:69 +#, fuzzy +#| msgid "Search" +msgctxt "alt. month" +msgid "March" +msgstr "بحث" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:70 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:71 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:72 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:73 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:74 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:75 +msgctxt "alt. month" +msgid "September" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:76 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:77 +msgctxt "alt. month" +msgid "November" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/dates.py:78 +msgctxt "alt. month" +msgid "December" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/ipv6.py:20 +msgid "This is not a valid IPv6 address." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/text.py:76 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/text.py:287 +#, fuzzy +#| msgid "More" +msgid "or" +msgstr "المزيد" + +#. Translators: This string is used as a separator between list elements +#: venv/lib/python3.13/site-packages/django/utils/text.py:306 +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:135 +msgid ", " +msgstr "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:8 +#, python-format +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:29 +msgid "Forbidden" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:30 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:34 +msgid "" +"You are seeing this message because this HTTPS site requires a “Referer " +"header” to be sent by your web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:40 +msgid "" +"If you have configured your browser to disable “Referer” headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for “same-" +"origin” requests." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:45 +msgid "" +"If you are using the tag or " +"including the “Referrer-Policy: no-referrer” header, please remove them. The " +"CSRF protection requires the “Referer” header to do strict referer checking. " +"If you’re concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:54 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:60 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for “same-origin” requests." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/csrf.py:66 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:44 +msgid "No year specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:64 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:115 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:214 +msgid "Date out of range" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:94 +msgid "No month specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:147 +msgid "No day specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:194 +msgid "No week specified" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:353 +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:384 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:680 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because " +"%(class_name)s.allow_future is False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/dates.py:720 +#, python-format +msgid "Invalid date string “%(datestr)s” given format “%(format)s”" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/detail.py:56 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:70 +msgid "Page is not “last”, nor can it be converted to an int." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:77 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/generic/list.py:173 +#, python-format +msgid "Empty list and “%(class_name)s.allow_empty” is False." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:49 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:51 +#, python-format +msgid "“%(path)s” does not exist" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/static.py:68 +#: venv/lib/python3.13/site-packages/django/views/templates/directory_index.html:8 +#: venv/lib/python3.13/site-packages/django/views/templates/directory_index.html:11 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:7 +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:204 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:206 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:208 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not " +"configured any URLs." +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:217 +msgid "Django Documentation" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:218 +msgid "Topics, references, & how-to’s" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:226 +msgid "Tutorial: A Polling App" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:227 +msgid "Get started with Django" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:235 +msgid "Django Community" +msgstr "" + +#: venv/lib/python3.13/site-packages/django/views/templates/default_urlconf.html:236 +msgid "Connect, get help, or contribute" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/permissions.py:18 +msgid "You do not have permission to upload files." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/permissions.py:25 +msgid "You must be logged in to upload files." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/validators.py:17 +#, python-format +msgid "File should be at most %(max_size)s MB." +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/views.py:41 +msgid "Invalid form data" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_ckeditor_5/widgets.py:43 +msgid "Check the correct settings.CKEDITOR_5_CONFIGS " +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:72 +msgid "Only POST method is allowed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:86 +msgid "Attachment module is disabled" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:93 +msgid "Only authenticated users are allowed" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:99 +msgid "No files were requested" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:140 +msgid "File size exceeds the limit allowed and cannot be saved" +msgstr "" + +#: venv/lib/python3.13/site-packages/django_summernote/views.py:160 +msgid "Failed to save attachment" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1311 +#, python-format +msgid "Attempting to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1316 +#, python-format +msgid "Connected to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/kombu/transport/qpid.py:1334 +#, python-format +msgid "Unable to connect to qpid with SASL mechanism %s" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/core.py:368 +#: venv/lib/python3.13/site-packages/typer/core.py:583 +msgid "required" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/core.py:630 +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:95 +msgid "Arguments" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:89 +#, fuzzy +#| msgid "Created at" +msgid "(deprecated) " +msgstr "تم الإنشاء في" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:90 +msgid "[default: {}]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:91 +msgid "[env var: {}]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:93 +msgid "[required]" +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:99 +msgid "Aborted." +msgstr "" + +#: venv/lib/python3.13/site-packages/typer/rich_utils.py:100 +#, python-brace-format +msgid "Try [blue]'{command_path} {help_option}'[/] for help." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/admin.py:40 +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold_list.py:337 +#, fuzzy +#| msgid "Select country" +msgid "Select record" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/admin.py:166 +#, fuzzy +#| msgid "Select country" +msgid "Select action" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:10 +msgid "Collapse" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:20 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:172 +msgid "Value" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:21 +msgid "Default" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:59 +msgid "Code" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:23 +msgid "Modified" +msgstr "تم التعديل" + +#: venv/lib/python3.13/site-packages/unfold/contrib/constance/templates/admin/constance/includes/results_list.html:65 +msgid "Reset to default" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:46 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:103 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:137 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/choice_filters.py:168 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:28 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:61 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/dropdown_filters.py:105 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:71 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/mixins.py:169 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/text_filters.py:29 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/admin/text_filters.py:60 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_date_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html:5 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/filter.html:5 +#, python-format +msgid " By %(filter_title)s " +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:193 +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:234 +msgid "To" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:258 +msgid "Date from" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/forms.py:274 +msgid "Date to" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html:30 +#, fuzzy +#| msgid "No data" +msgid "Not enough data." +msgstr "لا توجد بيانات" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/array.html:30 +#, fuzzy +#| msgid "Add New Candidate" +msgid "Add new item" +msgstr "إضافة مرشح جديد" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:7 +msgid "Paragraph" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:11 +msgid "Underlined" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:15 +msgid "Bold" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:19 +msgid "Italic" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:23 +msgid "Strike" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:35 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:39 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:43 +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:47 +msgid "Heading" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:55 +msgid "Quote" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:63 +msgid "Unordered list" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:67 +msgid "Ordered list" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:71 +msgid "Indent increase" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:75 +msgid "Indent decrease" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:83 +msgid "Undo" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:87 +msgid "Redo" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:95 +#, fuzzy +#| msgid "Enter email" +msgid "Enter an URL" +msgstr "أدخل البريد الإلكتروني" + +#: venv/lib/python3.13/site-packages/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html:102 +msgid "Unlink" +msgstr "فك الربط" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/change_form.html:8 +msgid "Object permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:13 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html:14 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:9 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:43 +msgid "Object" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html:16 +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:15 +msgid "Group" +msgstr "تجميع" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:7 +msgid "Group permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/group_form.html:67 +#, fuzzy +#| msgid "Manage" +msgid "Manage group" +msgstr "إدارة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:7 +msgid "User permissions" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/guardian/templates/unfold/guardian/user_form.html:67 +#, fuzzy +#| msgid "Manage" +msgid "Manage user" +msgstr "إدارة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_form.html:8 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html:4 +msgid "Export" +msgstr "تصدير" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html:4 +msgid "Import" +msgstr "استيراد" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:18 +#, python-format +msgid "" +"\n" +" Export %(len)s selected item.\n" +" " +msgid_plural "" +"\n" +" Export %(len)s selected items.\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/export.html:37 +msgid "This exporter will export the following fields" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html:10 +msgid "" +"Below is a preview of data to be imported. If you are satisfied with the " +"results, click 'Confirm import'" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html:15 +#, fuzzy +#| msgid "Confirm Delete" +msgid "Confirm import" +msgstr "تأكيد الحذف" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_errors.html:20 +#, fuzzy +#| msgid "Enter phone number" +msgid "Line number" +msgstr "أدخل رقم الهاتف" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_preview.html:24 +msgid "Skipped" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:6 +msgid "Some rows failed to validate" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:10 +msgid "" +"Please correct these errors in your data where possible, then reupload it " +"using the form above." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:22 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:40 +msgid "Row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:26 +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:44 +#, fuzzy +#| msgid "Error" +msgid "Errors" +msgstr "خطأ" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/import_validation.html:70 +msgid "Non field specific" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html:6 +msgid "This exporter will export the following fields: " +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html:8 +msgid "This importer will import the following fields: " +msgstr "" + +#. Translators: Model verbose name and instance representation, +#. suitable to be an item in a list. +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:99 +#, python-format +msgid "%(class_name)s %(instance)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/inlines/admin.py:111 +#, python-format +msgid "" +"Deleting %(class_name)s %(instance)s would require deleting the following " +"protected related objects: %(related_objects)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:8 +msgid "" +"Choose a date from the list below to revert to a previous version of this " +"object." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:28 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:57 +#, fuzzy +#| msgid "Country" +msgid "entry" +msgid_plural "entries" +msgstr[0] "الدولة" +msgstr[1] "الدولة" +msgstr[2] "الدولة" +msgstr[3] "الدولة" +msgstr[4] "الدولة" +msgstr[5] "الدولة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history.html:32 +msgid "This object doesn't have a change history." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_form.html:16 +msgid "" +"Press the 'Revert' button below to revert to this version of the object." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_form.html:20 +msgid "Press the 'Change History' button below to edit the history." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:19 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:55 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:10 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:26 +msgid "Date/time" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:27 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:63 +#, fuzzy +#| msgid "Created by" +msgid "Changed by" +msgstr "أنشأ بواسطة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:31 +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:77 +#, fuzzy +#| msgid "Change Stage" +msgid "Change reason" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/object_history_list.html:73 +msgid "None" +msgstr "لا شيء" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:8 +msgid "Revert" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/contrib/simple_history/templates/simple_history/submit_line.html:14 +#, fuzzy +#| msgid "Change Stage" +msgid "Change History" +msgstr "تغيير المرحلة" + +#: venv/lib/python3.13/site-packages/unfold/forms.py:71 +#, fuzzy +#| msgid "Select country" +msgid "Select action to run" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/forms.py:129 +msgid "" +"Raw passwords are not stored, so there is no way to see this user’s " +"password, but you can change the password using this form." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/mixins/base_model_admin.py:57 +#: venv/lib/python3.13/site-packages/unfold/mixins/base_model_admin.py:78 +#, fuzzy +#| msgid "Select country" +msgid "Select value" +msgstr "اختر الدولة" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:22 +msgid "Run the selected action" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:23 +msgid "Run" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:43 +msgid "Click here to select the objects across all pages" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:44 +#, python-format +msgid "Select all %(total_count)s %(module_name)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/actions.html:50 +msgid "Clear selection" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_list.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:9 +#, python-format +msgid "Models in the %(name)s application" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/app_list.html:48 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list.html:99 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:59 +msgid "You don’t have permission to view or edit anything." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/add_form.html:6 +msgid "After you've created a user, you’ll be able to edit more user options." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/change_password.html:19 +#, python-format +msgid "Enter a new password for the user %(username)s." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/auth/user/change_password.html:30 +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_form.html:29 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:30 +#, fuzzy +#| msgid "Password" +msgid "Change password" +msgstr "كلمة المرور" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_form_object_tools.html:6 +msgid "History" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_form_object_tools.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/edit_inline/stacked.html:77 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_title.html:33 +#, fuzzy +#| msgid "View on LinkedIn" +msgid "View on site" +msgstr "عرض على LinkedIn" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list.html:69 +msgid "Filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:32 +msgid "Select all rows" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:46 +#, fuzzy +#| msgid "Toggle user menu" +msgid "Toggle sorting" +msgstr "تبديل قائمة المستخدم" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:54 +msgid "Remove from sorting" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:60 +#, python-format +msgid "Sorting priority: %(priority_number)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/change_list_results.html:85 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/components/table.html:34 +msgid "Expand row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:16 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:32 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " +"following protected related objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:48 +#, python-format +msgid "" +"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " +"All of the following related items will be deleted:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_confirmation.html:55 +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:49 +msgid "Objects" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:15 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would result in deleting related " +"objects, but your account doesn't have permission to delete the following " +"types of objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:28 +#, python-format +msgid "" +"Deleting the selected %(objects_name)s would require deleting the following " +"protected related objects:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/delete_selected_confirmation.html:42 +#, python-format +msgid "" +"Are you sure you want to delete the selected %(objects_name)s? All of the " +"following objects and their related items will be deleted:" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/includes/object_delete_summary.html:5 +msgid "Summary" +msgstr "الملخص" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:15 +msgid "Welcome back to" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:26 +#, python-format +msgid "" +"You are authenticated as %(username)s, but are not authorized to access this " +"page. Would you like to login to a different account?" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:47 +msgid "Log in" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/login.html:55 +msgid "Forgotten your password or username?" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/object_history.html:60 +msgid "" +"This object doesn’t have a change history. It probably wasn’t added via this " +"admin site." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/pagination.html:12 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +msgid "Show all" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/search_form.html:18 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command.html:24 +msgid "Type to search" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:28 +msgid "Save and continue editing" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:30 +msgid "Save and view" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:37 +msgid "Save and add another" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/admin/submit_line.html:43 +msgid "Save as new" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:14 +msgid "You have been successfully logged out from the administration" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:18 +msgid "Thanks for spending some quality time with the web site today." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/logged_out.html:23 +msgid "Log in again" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_done.html:9 +msgid "Your password was changed." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/registration/password_change_form.html:18 +msgid "" +"Please enter your old password, for security’s sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:17 +#, fuzzy +#| msgid "View Candidate" +msgid "View site" +msgstr "عرض المرشح" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/account_links.html:40 +#, fuzzy +#| msgid "Sign out" +msgid "Log out" +msgstr "تسجيل الخروج" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/actions_row.html:4 +#, fuzzy +#| msgid "Actions" +msgid "More actions" +msgstr "الإجراءات" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/add_link.html:5 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/add_link.html:8 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:4 +#, python-format +msgid "Add %(name)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list.html:68 +#, fuzzy +#| msgid "Applications" +msgid "All applications" +msgstr "التقديمات" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/app_list_default.html:31 +msgid "Add" +msgstr "إضافة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "True" +msgstr "صحيح" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/boolean.html:4 +msgid "False" +msgstr "خطأ" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:16 +msgid "Hide counts" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:20 +#, fuzzy +#| msgid "Your account" +msgid "Show counts" +msgstr "حسابك" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/change_list_filter_actions.html:27 +msgid "Clear all filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_history.html:7 +msgid "Recent searches" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_history.html:49 +msgid "No recent searches" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:44 +#, fuzzy +#| msgid "No templates match your search \"%(query)s\"." +msgid "No results matching your query" +msgstr "لا توجد قوالب تطابق بحثك \"%(query)s\"." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:57 +msgid "Loading more results..." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/command_results.html:62 +#, python-format +msgid "" +"\n" +" Found %(counter)s result in %(time)s seconds\n" +" " +msgid_plural "" +"\n" +" Found %(counter)s results in %(time)s seconds\n" +" " +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/delete_submit_line.html:5 +msgid "No, take me back" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/delete_submit_line.html:9 +msgid "Yes, I’m sure" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/display_header.html:10 +msgid "Record picture" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/edit_inline/tabular_heading.html:21 +#, fuzzy +#| msgid "Delete" +msgid "Delete?" +msgstr "حذف" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:12 +#, fuzzy +#| msgid "No meetings found." +msgid "No results found" +msgstr "لم يتم العثور على اجتماعات." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:16 +msgid "" +"This page yielded into no results. Create a new item or reset your filters." +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/empty_results.html:30 +msgid "Reset filters" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/header_back_button.html:7 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/header_back_button.html:15 +msgid "Go back" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/history.html:9 +#, fuzzy +#| msgid "Management Actions" +msgid "Recent actions" +msgstr "إجراءات الإدارة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/history.html:28 +msgid "Unknown content" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/messages/errornote.html:5 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/pagination_infinite.html:5 +#, fuzzy +#| msgid "Preview" +msgid "Previous" +msgstr "معاينة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +#, python-format +msgid "%(counter)s result" +msgid_plural "%(counter)s results" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" +msgstr[4] "" +msgstr[5] "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/popup_header.html:14 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/welcomemsg.html:25 +#, python-format +msgid "%(full_result_count)s total" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:10 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:40 +#, fuzzy +#| msgid "Search templates by name..." +msgid "Search apps and models..." +msgstr "البحث عن القوالب بالاسم..." + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/search.html:41 +#, fuzzy +#| msgid "Toggle navigation" +msgid "Filter navigation items" +msgstr "تبديل التنقل" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_branding.html:3 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/site_branding.html:6 +msgid "Django administration" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/tab_items.html:15 +msgid "General" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:16 +msgid "Dark" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:23 +msgid "Light" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/theme_switch.html:30 +msgid "System" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/helpers/unauthenticated_header.html:6 +msgid "Return to site" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input.html:6 +#, fuzzy +#| msgid "Interview" +msgid "Image preview" +msgstr "المقابلة" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input.html:24 +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/clearable_file_input_small.html:17 +msgid "Choose file to upload" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:16 +#, python-format +msgid "Change selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:26 +#, python-format +msgid "Add another %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:35 +#, python-format +msgid "View selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold/widgets/related_widget_wrapper.html:45 +#, python-format +msgid "Delete selected %(model)s" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templates/unfold_crispy/layout/table_inline_formset.html:65 +msgid "Add row" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold.py:770 +msgid "Welcome" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/templatetags/unfold_list.py:118 +msgid "Select all objects on this page for an action" +msgstr "" + +#: venv/lib/python3.13/site-packages/unfold/widgets.py:821 +#, fuzzy +#| msgid "Select country" +msgid "Select currency" +msgstr "اختر الدولة" + +#, python-format +#~ msgid "" +#~ "Cannot transition from \"%(current)s\" to \"%(new)s\". Allowed " +#~ "transitions: %(allowed)s" +#~ msgstr "" +#~ "لا يمكن الانتقال من \"%(current)s\" إلى \"%(new)s\". الانتقالات المسموح " +#~ "بها: %(allowed)s" + +#~ msgid "Enter title" +#~ msgstr "أدخل العنوان" + +#~ msgid "Save Material" +#~ msgstr "حفظ المادة" + +#~ msgid "Help & Support" +#~ msgstr "المساعدة والدعم" + +#~ msgid "Application URL" +#~ msgstr "رابط التقديم" + +#~ msgid "Full URL where candidates will apply" +#~ msgstr "الرابط الكامل حيث سيقدم المرشحون" + +#~ msgid "Desired Start Date" +#~ msgstr "تاريخ البدء المطلوب" + +#~ msgid "Post Reach Field" +#~ msgstr "حقل وصول المنشور" + +#~ msgid "Hashtags" +#~ msgstr "الوسوم" + +#~ msgid "Start Date:" +#~ msgstr "تاريخ البدء:" + +#~ msgid "Info" +#~ msgstr "معلومات" + +#~ msgid "View All Existing Forms" +#~ msgstr "عرض جميع النماذج الموجودة" + +#~ msgid "Reports To:" +#~ msgstr "يقدم تقاريره إلى:" + +#~ msgid "Host Video" +#~ msgstr "فيديو المضيف" + +#~ msgid "Are you sure?" +#~ msgstr "هل أنت متأكد؟" + +#~ msgid "Zoom API Response" +#~ msgstr "استجابة واجهة برمجة تطبيقات Zoom" + +#~ msgid "Start Time (ISO 8601):" +#~ msgstr "وقت البدء (ISO 8601):" + +#~ msgid "Duration (minutes):" +#~ msgstr "المدة (دقائق):" + +#~ msgid "Resume Document" +#~ msgstr "وثيقة السيرة الذاتية" + +#~ msgid "Parsed Data" +#~ msgstr "البيانات المحللة" + +#~ msgid "Activity" +#~ msgstr "النشاط" + +#~ msgid "Structured Resume Data" +#~ msgstr "بيانات السيرة الذاتية المنظمة" + +#~ msgid "" +#~ "Activity feed (e.g., stage changes, notes, interview history) will appear " +#~ "here." +#~ msgstr "" +#~ "سجل النشاط (مثلاً، تغييرات المرحلة، ملاحظات، سجل المقابلات) سيظهر هنا." diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 25de3e8..af604ce 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -1,1432 +1,1008 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-08 13:38+0300\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: recruitment/forms.py:14 recruitment/models.py:200 + msgid "First Name" msgstr "" -#: recruitment/forms.py:15 recruitment/models.py:201 msgid "Last Name" msgstr "" -#: recruitment/forms.py:16 recruitment/models.py:203 -#: templates/recruitment/candidate_list.html:152 msgid "Phone" msgstr "" -#: recruitment/forms.py:17 recruitment/models.py:202 -#: templates/recruitment/candidate_detail.html:242 -#: templates/recruitment/candidate_list.html:151 msgid "Email" msgstr "" -#: recruitment/forms.py:18 recruitment/models.py:204 -#: templates/recruitment/candidate_detail.html:218 msgid "Resume" msgstr "" -#: recruitment/forms.py:19 msgid "Application Stage" msgstr "" -#: recruitment/forms.py:22 msgid "Enter first name" msgstr "" -#: recruitment/forms.py:23 msgid "Enter last name" msgstr "" -#: recruitment/forms.py:24 msgid "Enter phone number" msgstr "" -#: recruitment/forms.py:25 msgid "Enter email" msgstr "" -#: recruitment/forms.py:50 msgid "Submit" msgstr "" -#: recruitment/forms.py:60 msgid "New Application Stage" msgstr "" -#: recruitment/forms.py:85 recruitment/models.py:180 recruitment/models.py:206 -#: templates/jobs/job_detail.html:420 -#: templates/recruitment/candidate_list.html:154 msgid "Applied" msgstr "" -#: recruitment/forms.py:92 msgid "Please select a stage." msgstr "" -#: recruitment/forms.py:101 -#, python-format -msgid "" -"Cannot transition from \"%(current)s\" to \"%(new)s\". Allowed transitions: " -"%(allowed)s" -msgstr "" - -#: recruitment/forms.py:116 recruitment/models.py:306 -#: templates/meetings/create_meeting.html:162 msgid "Topic" msgstr "" -#: recruitment/forms.py:117 recruitment/models.py:308 recruitment/models.py:727 -#: templates/meetings/create_meeting.html:166 -#: templates/meetings/list_meetings.html:260 -#: templates/meetings/meeting_details.html:29 msgid "Start Time" msgstr "" -#: recruitment/forms.py:118 recruitment/models.py:309 -#: templates/meetings/list_meetings.html:265 -#: templates/meetings/meeting_details.html:33 msgid "Duration" msgstr "" -#: recruitment/forms.py:121 msgid "Enter meeting topic" msgstr "" -#: recruitment/forms.py:123 msgid "60" msgstr "" -#: recruitment/forms.py:137 templates/meetings/create_meeting.html:180 -#: templates/meetings/list_meetings.html:242 msgid "Create Meeting" msgstr "" -#: recruitment/forms.py:147 recruitment/models.py:290 -#: templates/recruitment/training_list.html:150 -#: templates/recruitment/training_update.html:144 msgid "Title" msgstr "" -#: recruitment/forms.py:148 recruitment/models.py:291 -#: templates/recruitment/training_update.html:158 msgid "Content" msgstr "" -#: recruitment/forms.py:149 recruitment/models.py:292 -#: templates/recruitment/training_update.html:150 msgid "Video Link" msgstr "" -#: recruitment/forms.py:150 recruitment/models.py:293 -#: templates/recruitment/training_update.html:166 msgid "File" msgstr "" -#: recruitment/forms.py:153 msgid "Enter title" msgstr "" -#: recruitment/forms.py:154 msgid "Enter material content" msgstr "" -#: recruitment/forms.py:155 msgid "https://www.youtube.com/watch?v=..." msgstr "" -#: recruitment/forms.py:174 msgid "Save Material" msgstr "" -#: recruitment/forms.py:377 recruitment/models.py:199 -#: templates/recruitment/candidate_list.html:153 msgid "Job" msgstr "" -#: recruitment/forms.py:378 msgid "Template Name" msgstr "" -#: recruitment/forms.py:379 recruitment/models.py:542 msgid "Description" msgstr "" -#: recruitment/forms.py:380 recruitment/models.py:576 msgid "Active" msgstr "" -#: recruitment/forms.py:385 msgid "Enter template name" msgstr "" -#: recruitment/forms.py:391 msgid "Enter template description (optional)" msgstr "" -#: recruitment/forms.py:410 msgid "Create Template" msgstr "" -#: recruitment/models.py:13 msgid "Created at" msgstr "" -#: recruitment/models.py:14 msgid "Updated at" msgstr "" -#: recruitment/models.py:15 msgid "Slug" msgstr "" -#: recruitment/models.py:120 recruitment/models.py:714 msgid "Hiring Agency" msgstr "" -#: recruitment/models.py:121 msgid "External agency responsible for sourcing candidates for this role" msgstr "" -#: recruitment/models.py:181 msgid "Exam" msgstr "" -#: recruitment/models.py:182 templates/jobs/job_detail.html:426 msgid "Interview" msgstr "" -#: recruitment/models.py:183 templates/jobs/job_detail.html:432 msgid "Offer" msgstr "" -#: recruitment/models.py:185 msgid "Passed" msgstr "" -#: recruitment/models.py:186 msgid "Failed" msgstr "" -#: recruitment/models.py:188 msgid "Accepted" msgstr "" -#: recruitment/models.py:189 msgid "Rejected" msgstr "" -#: recruitment/models.py:205 msgid "Parsed Summary" msgstr "" -#: recruitment/models.py:207 msgid "Stage" msgstr "" -#: recruitment/models.py:209 msgid "Exam Date" msgstr "" -#: recruitment/models.py:210 msgid "Exam Status" msgstr "" -#: recruitment/models.py:211 recruitment/models.py:744 msgid "Interview Date" msgstr "" -#: recruitment/models.py:212 msgid "Interview Status" msgstr "" -#: recruitment/models.py:213 msgid "Offer Date" msgstr "" -#: recruitment/models.py:214 msgid "Offer Status" msgstr "" -#: recruitment/models.py:215 msgid "Join Date" msgstr "" -#: recruitment/models.py:230 msgid "Submitted by Agency" msgstr "" -#: recruitment/models.py:234 msgid "Candidate" msgstr "" -#: recruitment/models.py:235 templates/base.html:298 -#: templates/jobs/job_detail.html:414 msgid "Candidates" msgstr "" -#: recruitment/models.py:294 msgid "Created by" msgstr "" -#: recruitment/models.py:297 msgid "Training Material" msgstr "" -#: recruitment/models.py:298 templates/recruitment/training_list.html:4 -#: templates/recruitment/training_list.html:126 msgid "Training Materials" msgstr "" -#: recruitment/models.py:307 templates/meetings/meeting_details.html:25 msgid "Meeting ID" msgstr "" -#: recruitment/models.py:310 msgid "Timezone" msgstr "" -#: recruitment/models.py:311 templates/meetings/list_meetings.html:286 msgid "Join URL" msgstr "" -#: recruitment/models.py:312 templates/meetings/meeting_details.html:62 msgid "Participant Video" msgstr "" -#: recruitment/models.py:313 templates/meetings/meeting_details.html:66 msgid "Join Before Host" msgstr "" -#: recruitment/models.py:314 templates/meetings/meeting_details.html:70 msgid "Mute Upon Entry" msgstr "" -#: recruitment/models.py:315 templates/meetings/meeting_details.html:74 msgid "Waiting Room" msgstr "" -#: recruitment/models.py:317 msgid "Zoom Gateway Response" msgstr "" -#: recruitment/models.py:532 msgid "Source Name" msgstr "" -#: recruitment/models.py:533 recruitment/models.py:538 msgid "e.g., ATS, ERP " msgstr "" -#: recruitment/models.py:537 msgid "Source Type" msgstr "" -#: recruitment/models.py:543 msgid "A description of the source" msgstr "" -#: recruitment/models.py:548 recruitment/models.py:667 msgid "IP Address" msgstr "" -#: recruitment/models.py:549 msgid "The IP address of the source" msgstr "" -#: recruitment/models.py:558 msgid "API Key" msgstr "" -#: recruitment/models.py:559 msgid "API key for authentication (will be encrypted)" msgstr "" -#: recruitment/models.py:565 msgid "API Secret" msgstr "" -#: recruitment/models.py:566 msgid "API secret for authentication (will be encrypted)" msgstr "" -#: recruitment/models.py:571 msgid "Trusted IP Addresses" msgstr "" -#: recruitment/models.py:572 msgid "Comma-separated list of trusted IP addresses" msgstr "" -#: recruitment/models.py:577 msgid "Whether this source is active for integration" msgstr "" -#: recruitment/models.py:582 msgid "Integration Version" msgstr "" -#: recruitment/models.py:583 msgid "Version of the integration protocol" msgstr "" -#: recruitment/models.py:588 msgid "Last Sync At" msgstr "" -#: recruitment/models.py:589 msgid "Timestamp of the last successful synchronization" msgstr "" -#: recruitment/models.py:601 msgid "Sync Status" msgstr "" -#: recruitment/models.py:610 recruitment/models.py:630 msgid "Source" msgstr "" -#: recruitment/models.py:611 msgid "Sources" msgstr "" -#: recruitment/models.py:619 msgid "Request" msgstr "" -#: recruitment/models.py:620 msgid "Response" msgstr "" -#: recruitment/models.py:621 msgid "Error" msgstr "" -#: recruitment/models.py:622 msgid "Sync" msgstr "" -#: recruitment/models.py:623 templates/jobs/create_job.html:392 msgid "Create Job" msgstr "" -#: recruitment/models.py:624 msgid "Update Job" msgstr "" -#: recruitment/models.py:635 msgid "Action" msgstr "" -#: recruitment/models.py:640 msgid "Endpoint" msgstr "" -#: recruitment/models.py:645 msgid "HTTP Method" msgstr "" -#: recruitment/models.py:650 msgid "Request Data" msgstr "" -#: recruitment/models.py:655 msgid "Response Data" msgstr "" -#: recruitment/models.py:660 msgid "Status Code" msgstr "" -#: recruitment/models.py:664 msgid "Error Message" msgstr "" -#: recruitment/models.py:672 msgid "User Agent" msgstr "" -#: recruitment/models.py:677 msgid "Processing Time (seconds)" msgstr "" -#: recruitment/models.py:685 msgid "Integration Log" msgstr "" -#: recruitment/models.py:686 msgid "Integration Logs" msgstr "" -#: recruitment/models.py:701 msgid "Agency Name" msgstr "" -#: recruitment/models.py:702 msgid "Contact Person" msgstr "" -#: recruitment/models.py:706 msgid "Internal notes about the agency" msgstr "" -#: recruitment/models.py:707 msgid "Select country" msgstr "" -#: recruitment/models.py:715 msgid "Hiring Agencies" msgstr "" -#: recruitment/models.py:724 msgid "Start Date" msgstr "" -#: recruitment/models.py:725 msgid "End Date" msgstr "" -#: recruitment/models.py:726 msgid "Working Days" msgstr "" -#: recruitment/models.py:728 msgid "End Time" msgstr "" -#: recruitment/models.py:729 msgid "Break Start Time" msgstr "" -#: recruitment/models.py:730 msgid "Break End Time" msgstr "" -#: recruitment/models.py:731 msgid "Interview Duration (minutes)" msgstr "" -#: recruitment/models.py:732 msgid "Buffer Time (minutes)" msgstr "" -#: recruitment/models.py:745 msgid "Interview Time" msgstr "" -#: recruitment/models.py:749 msgid "Scheduled" msgstr "" -#: recruitment/models.py:750 msgid "Confirmed" msgstr "" -#: recruitment/models.py:751 msgid "Cancelled" msgstr "" -#: recruitment/models.py:752 msgid "Completed" msgstr "" -#: templates/base.html:7 msgid "King Abdullah Academic University Hospital - Applicant Tracking System" msgstr "" -#: templates/base.html:8 msgid "University ATS" msgstr "" -#: templates/base.html:258 msgid "Saudi Vision 2030" msgstr "" -#: templates/base.html:259 msgid "King Abdullah Academic University Hospital" msgstr "" -#: templates/base.html:272 msgid "Toggle navigation" msgstr "" -#: templates/base.html:282 msgid "Dashboard" msgstr "" -#: templates/base.html:290 msgid "Jobs" msgstr "" -#: templates/base.html:309 msgid "Training" msgstr "" -#: templates/base.html:320 templates/base.html:340 msgid "Meetings" msgstr "" -#: templates/base.html:330 templates/forms/form_templates_list.html:190 msgid "Form Templates" msgstr "" -#: templates/base.html:337 msgid "More" msgstr "" -#: templates/base.html:341 msgid "Schedule" msgstr "" -#: templates/base.html:343 msgid "Active Jobs" msgstr "" -#: templates/base.html:344 msgid "Draft Jobs" msgstr "" -#: templates/base.html:346 msgid "All Candidates" msgstr "" -#: templates/base.html:347 msgid "New Candidates" msgstr "" -#: templates/base.html:356 msgid "Toggle language menu" msgstr "" -#: templates/base.html:392 msgid "Toggle user menu" msgstr "" -#: templates/base.html:396 msgid "Your account" msgstr "" -#: templates/base.html:419 msgid "My Profile" msgstr "" -#: templates/base.html:420 templates/meetings/meeting_details.html:56 msgid "Settings" msgstr "" -#: templates/base.html:421 msgid "Activity Log" msgstr "" -#: templates/base.html:422 msgid "Help & Support" msgstr "" -#: templates/base.html:428 msgid "Connect LinkedIn" msgstr "" -#: templates/base.html:435 msgid "LinkedIn Connected" msgstr "" -#: templates/base.html:447 msgid "Sign out" msgstr "" -#: templates/base.html:450 msgid "Sign Out" msgstr "" -#: templates/base.html:466 msgid "Close" msgstr "" -#: templates/base.html:478 msgid "King Abdullah Academic University Hospital (KAAUH)." msgstr "" -#: templates/base.html:479 msgid "All rights reserved." msgstr "" -#: templates/base.html:511 msgid "Are you sure you want to sign out?" msgstr "" -#: templates/forms/form_templates_list.html:208 msgid "Search templates by name..." msgstr "" -#: templates/forms/form_templates_list.html:243 msgid "ago" msgstr "" -#: templates/forms/form_templates_list.html:253 msgid "Stages" msgstr "" -#: templates/forms/form_templates_list.html:257 msgid "Fields" msgstr "" -#: templates/forms/form_templates_list.html:264 msgid "No description provided" msgstr "" -#: templates/forms/form_templates_list.html:274 msgid "Preview" msgstr "" -#: templates/forms/form_templates_list.html:277 -#: templates/recruitment/candidate_list.html:178 -#: templates/recruitment/training_list.html:168 msgid "Edit" msgstr "" -#: templates/forms/form_templates_list.html:282 -#: templates/includes/delete_modal.html:26 -#: templates/meetings/list_meetings.html:307 -#: templates/recruitment/candidate_list.html:181 -#: templates/recruitment/training_list.html:171 -#: templates/recruitment/training_update.html:184 msgid "Delete" msgstr "" -#: templates/forms/form_templates_list.html:326 msgid "No Form Templates Found" msgstr "" -#: templates/forms/form_templates_list.html:329 -#, python-format msgid "No templates match your search \"%(query)s\"." msgstr "" -#: templates/forms/form_templates_list.html:331 msgid "You haven't created any form templates yet." msgstr "" -#: templates/forms/form_templates_list.html:335 msgid "Create Your First Template" msgstr "" -#: templates/forms/form_wizard.html:7 templates/forms/form_wizard.html:470 -#: templates/jobs/job_detail_candidate.html:7 msgid "Application Form" msgstr "" -#: templates/forms/form_wizard.html:439 -#: templates/jobs/job_detail_candidate.html:121 msgid "KAAUH IMAGE" msgstr "" -#: templates/forms/form_wizard.html:449 -#: templates/jobs/job_detail_candidate.html:132 msgid "Applications" msgstr "" -#: templates/forms/form_wizard.html:452 -#: templates/jobs/job_detail_candidate.html:135 msgid "Profile" msgstr "" -#: templates/forms/form_wizard.html:480 msgid "Review Your Application" msgstr "" -#: templates/forms/form_wizard.html:487 msgid "Back" msgstr "" -#: templates/forms/form_wizard.html:491 msgid "Next" msgstr "" -#: templates/forms/form_wizard.html:495 msgid "Submit Application" msgstr "" -#: templates/includes/delete_modal.html:11 msgid "Confirm Delete" msgstr "" -#: templates/includes/delete_modal.html:16 msgid "Are you sure you want to delete this item?" msgstr "" -#: templates/includes/delete_modal.html:19 templates/jobs/create_job.html:389 -#: templates/meetings/create_meeting.html:184 -#: templates/meetings/update_meeting.html:222 msgid "Cancel" msgstr "" -#: templates/includes/search_form.html:11 msgid "Search..." msgstr "" -#: templates/includes/search_form.html:13 msgid "Search" msgstr "" -#: templates/jobs/create_job.html:113 msgid "Create New Job Posting" msgstr "" -#: templates/jobs/create_job.html:121 msgid "Basic Information" msgstr "" -#: templates/jobs/create_job.html:127 msgid "Job Title" msgstr "" -#: templates/jobs/create_job.html:137 msgid "Job Type" msgstr "" -#: templates/jobs/create_job.html:148 msgid "Department" msgstr "" -#: templates/jobs/create_job.html:158 msgid "Position Number" msgstr "" -#: templates/jobs/create_job.html:169 msgid "Workplace Type" msgstr "" -#: templates/jobs/create_job.html:179 -#: templates/recruitment/training_list.html:151 msgid "Created By" msgstr "" -#: templates/jobs/create_job.html:193 msgid "Location" msgstr "" -#: templates/jobs/create_job.html:199 msgid "City" msgstr "" -#: templates/jobs/create_job.html:209 msgid "State/Province" msgstr "" -#: templates/jobs/create_job.html:219 msgid "Country" msgstr "" -#: templates/jobs/create_job.html:233 msgid "Job Details" msgstr "" -#: templates/jobs/create_job.html:239 templates/jobs/job_detail.html:341 msgid "Job Description" msgstr "" -#: templates/jobs/create_job.html:250 msgid "Qualifications and Requirements" msgstr "" -#: templates/jobs/create_job.html:261 msgid "Salary Range" msgstr "" -#: templates/jobs/create_job.html:271 templates/jobs/job_detail.html:353 msgid "Benefits" msgstr "" -#: templates/jobs/create_job.html:285 msgid "Application Information" msgstr "" -#: templates/jobs/create_job.html:291 msgid "Application URL" msgstr "" -#: templates/jobs/create_job.html:297 msgid "Full URL where candidates will apply" msgstr "" -#: templates/jobs/create_job.html:303 msgid "Application Deadline" msgstr "" -#: templates/jobs/create_job.html:313 msgid "Desired Start Date" msgstr "" -#: templates/jobs/create_job.html:324 templates/jobs/job_detail.html:363 msgid "Application Instructions" msgstr "" -#: templates/jobs/create_job.html:338 msgid "Post Reach Field" msgstr "" -#: templates/jobs/create_job.html:344 msgid "Hashtags" msgstr "" -#: templates/jobs/create_job.html:358 templates/jobs/job_detail.html:545 msgid "Internal Information" msgstr "" -#: templates/jobs/create_job.html:364 msgid "Reports To" msgstr "" -#: templates/jobs/create_job.html:374 msgid "Open Positions" msgstr "" -#: templates/jobs/job_detail.html:262 -#: templates/recruitment/candidate_detail.html:236 msgid "Core Details" msgstr "" -#: templates/jobs/job_detail.html:267 msgid "Description & Requirements" msgstr "" -#: templates/jobs/job_detail.html:273 msgid "Application" msgstr "" -#: templates/jobs/job_detail.html:284 msgid "Administrative & Location" msgstr "" -#: templates/jobs/job_detail.html:287 msgid "Department:" msgstr "" -#: templates/jobs/job_detail.html:290 msgid "Position No:" msgstr "" -#: templates/jobs/job_detail.html:293 msgid "Job Type:" msgstr "" -#: templates/jobs/job_detail.html:296 msgid "Workplace:" msgstr "" -#: templates/jobs/job_detail.html:299 msgid "Location:" msgstr "" -#: templates/jobs/job_detail.html:302 msgid "Created By:" msgstr "" -#: templates/jobs/job_detail.html:305 msgid "Financial & Timeline" msgstr "" -#: templates/jobs/job_detail.html:311 msgid "Salary:" msgstr "" -#: templates/jobs/job_detail.html:319 msgid "Start Date:" msgstr "" -#: templates/jobs/job_detail.html:327 msgid "Deadline:" msgstr "" -#: templates/jobs/job_detail.html:329 msgid "EXPIRED" msgstr "" -#: templates/jobs/job_detail.html:347 msgid "Required Qualifications" msgstr "" -#: templates/jobs/job_detail.html:375 msgid "Edit Job" msgstr "" -#: templates/jobs/job_detail.html:380 msgid "Upload Image for Post" msgstr "" -#: templates/jobs/job_detail.html:395 msgid "Applicants" msgstr "" -#: templates/jobs/job_detail.html:400 msgid "Manage" msgstr "" -#: templates/jobs/job_detail.html:405 msgid "Info" msgstr "" -#: templates/jobs/job_detail.html:464 msgid "View All Applicants" msgstr "" -#: templates/jobs/job_detail.html:471 msgid "No applicants yet" msgstr "" -#: templates/jobs/job_detail.html:472 msgid "Candidates will appear here once they apply for this position." msgstr "" -#: templates/jobs/job_detail.html:481 msgid "LinkedIn Integration" msgstr "" -#: templates/jobs/job_detail.html:485 msgid "Posted successfully!" msgstr "" -#: templates/jobs/job_detail.html:489 msgid "View on LinkedIn" msgstr "" -#: templates/jobs/job_detail.html:493 msgid "Posted on:" msgstr "" -#: templates/jobs/job_detail.html:496 msgid "This job has not been posted to LinkedIn yet." msgstr "" -#: templates/jobs/job_detail.html:504 msgid "Re-post to LinkedIn" msgstr "" -#: templates/jobs/job_detail.html:504 msgid "Post to LinkedIn" msgstr "" -#: templates/jobs/job_detail.html:510 msgid "You need to" msgstr "" -#: templates/jobs/job_detail.html:510 msgid "authenticate with LinkedIn" msgstr "" -#: templates/jobs/job_detail.html:510 msgid "first." msgstr "" -#: templates/jobs/job_detail.html:517 msgid "Error:" msgstr "" -#: templates/jobs/job_detail.html:523 msgid "Form Management" msgstr "" -#: templates/jobs/job_detail.html:526 msgid "Manage the custom application forms associated with this job posting." msgstr "" -#: templates/jobs/job_detail.html:530 msgid "Create New Form" msgstr "" -#: templates/jobs/job_detail.html:534 msgid "View All Existing Forms" msgstr "" -#: templates/jobs/job_detail.html:538 -#: templates/recruitment/candidate_create.html:131 msgid "Create Candidate" msgstr "" -#: templates/jobs/job_detail.html:547 msgid "Internal Job ID:" msgstr "" -#: templates/jobs/job_detail.html:548 msgid "Created:" msgstr "" -#: templates/jobs/job_detail.html:549 msgid "Last Updated:" msgstr "" -#: templates/jobs/job_detail.html:551 msgid "Reports To:" msgstr "" -#: templates/jobs/job_detail.html:557 msgid "Back to Jobs" msgstr "" -#: templates/jobs/job_detail_candidate.html:144 msgid "Job Overview" msgstr "" -#: templates/meetings/create_meeting.html:4 msgid "Create Zoom Meeting" msgstr "" -#: templates/meetings/create_meeting.html:151 msgid "Create New Zoom Meeting" msgstr "" -#: templates/meetings/create_meeting.html:155 -#: templates/meetings/meeting_details.html:19 msgid "Back to Meetings" msgstr "" -#: templates/meetings/create_meeting.html:170 msgid "Duration (minutes)" msgstr "" -#: templates/meetings/list_meetings.html:4 -#: templates/meetings/list_meetings.html:233 msgid "Zoom Meetings" msgstr "" -#: templates/meetings/list_meetings.html:255 msgid "ID" msgstr "" -#: templates/meetings/list_meetings.html:270 msgid "Status" msgstr "" -#: templates/meetings/list_meetings.html:274 msgid "Waiting" msgstr "" -#: templates/meetings/list_meetings.html:276 msgid "Started" msgstr "" -#: templates/meetings/list_meetings.html:278 msgid "Ended" msgstr "" -#: templates/meetings/list_meetings.html:289 -#: templates/meetings/meeting_details.html:45 msgid "Join Meeting" msgstr "" -#: templates/meetings/list_meetings.html:296 -#: templates/recruitment/candidate_list.html:174 -#: templates/recruitment/candidate_update.html:104 -#: templates/recruitment/training_list.html:164 -#: templates/recruitment/training_update.html:119 msgid "View" msgstr "" -#: templates/meetings/list_meetings.html:302 msgid "Update" msgstr "" -#: templates/meetings/list_meetings.html:356 msgid "No meetings found." msgstr "" -#: templates/meetings/list_meetings.html:362 msgid "Create Your First Meeting" msgstr "" -#: templates/meetings/meeting_details.html:4 msgid "Meeting Details" msgstr "" -#: templates/meetings/meeting_details.html:23 -#: templates/meetings/update_meeting.html:191 msgid "Meeting Information" msgstr "" -#: templates/meetings/meeting_details.html:37 msgid "Host Email" msgstr "" -#: templates/meetings/meeting_details.html:44 msgid "Join Information" msgstr "" -#: templates/meetings/meeting_details.html:48 msgid "Password" msgstr "" -#: templates/meetings/meeting_details.html:58 msgid "Host Video" msgstr "" -#: templates/meetings/meeting_details.html:81 msgid "View API Response" msgstr "" -#: templates/meetings/meeting_details.html:83 -#: templates/meetings/update_meeting.html:218 msgid "Update Meeting" msgstr "" -#: templates/meetings/meeting_details.html:86 msgid "Are you sure?" msgstr "" -#: templates/meetings/meeting_details.html:86 msgid "Delete Meeting" msgstr "" -#: templates/meetings/meeting_details.html:93 msgid "Zoom API Response" msgstr "" -#: templates/meetings/update_meeting.html:3 -#: templates/meetings/update_meeting.html:174 msgid "Update Zoom Meeting" msgstr "" -#: templates/meetings/update_meeting.html:175 msgid "Modify the details of your scheduled meeting" msgstr "" -#: templates/meetings/update_meeting.html:197 msgid "Topic:" msgstr "" -#: templates/meetings/update_meeting.html:202 msgid "Start Time (ISO 8601):" msgstr "" -#: templates/meetings/update_meeting.html:209 msgid "Duration (minutes):" msgstr "" -#: templates/recruitment/candidate_create.html:94 msgid "Create New Candidate" msgstr "" -#: templates/recruitment/candidate_create.html:96 msgid "Enter details to create a new candidate record." msgstr "" -#: templates/recruitment/candidate_create.html:99 -#: templates/recruitment/candidate_create.html:101 -#: templates/recruitment/candidate_detail.html:326 -#: templates/recruitment/candidate_update.html:97 -#: templates/recruitment/candidate_update.html:99 -#: templates/recruitment/training_create.html:124 -#: templates/recruitment/training_create.html:126 -#: templates/recruitment/training_update.html:112 -#: templates/recruitment/training_update.html:114 msgid "Back to List" msgstr "" -#: templates/recruitment/candidate_create.html:112 msgid "Candidate Information" msgstr "" -#: templates/recruitment/candidate_detail.html:191 msgid "Stage:" msgstr "" -#: templates/recruitment/candidate_detail.html:196 msgid "Applied for:" msgstr "" -#: templates/recruitment/candidate_detail.html:202 msgid "Change Stage" msgstr "" -#: templates/recruitment/candidate_detail.html:212 msgid "Contact & Job" msgstr "" -#: templates/recruitment/candidate_detail.html:225 msgid "Summary" msgstr "" -#: templates/recruitment/candidate_detail.html:251 msgid "Position Applied" msgstr "" -#: templates/recruitment/candidate_detail.html:260 msgid "Applied Date" msgstr "" -#: templates/recruitment/candidate_detail.html:277 msgid "Resume Document" msgstr "" -#: templates/recruitment/candidate_detail.html:285 msgid "Download Resume" msgstr "" -#: templates/recruitment/candidate_detail.html:294 msgid "AI Generated Summary" msgstr "" -#: templates/recruitment/candidate_detail.html:312 msgid "Management Actions" msgstr "" -#: templates/recruitment/candidate_detail.html:320 msgid "Edit Details" msgstr "" -#: templates/recruitment/candidate_detail.html:322 msgid "Are you sure you want to delete this candidate?" msgstr "" -#: templates/recruitment/candidate_detail.html:323 msgid "Delete Candidate" msgstr "" -#: templates/recruitment/candidate_detail.html:338 msgid "Parsed Data" msgstr "" -#: templates/recruitment/candidate_detail.html:343 msgid "Activity" msgstr "" -#: templates/recruitment/candidate_detail.html:352 msgid "Structured Resume Data" msgstr "" -#: templates/recruitment/candidate_detail.html:372 -msgid "" -"Activity feed (e.g., stage changes, notes, interview history) will appear " -"here." -msgstr "" - -#: templates/recruitment/candidate_list.html:129 msgid "Candidate Profiles" msgstr "" -#: templates/recruitment/candidate_list.html:138 msgid "Add New Candidate" msgstr "" -#: templates/recruitment/candidate_list.html:150 msgid "Name" msgstr "" -#: templates/recruitment/candidate_list.html:155 -#: templates/recruitment/training_list.html:152 msgid "Created" msgstr "" -#: templates/recruitment/candidate_list.html:156 -#: templates/recruitment/training_list.html:153 msgid "Actions" msgstr "" -#: templates/recruitment/candidate_list.html:233 msgid "No candidates found." msgstr "" -#: templates/recruitment/candidate_list.html:234 msgid "Start by adding a new profile or adjusting your search filters." msgstr "" -#: templates/recruitment/candidate_list.html:238 msgid "Add Your First Candidate" msgstr "" -#: templates/recruitment/candidate_update.html:92 msgid "Update Candidate:" msgstr "" -#: templates/recruitment/candidate_update.html:94 msgid "Edit candidate information and details" msgstr "" -#: templates/recruitment/candidate_update.html:102 msgid "View Candidate" msgstr "" -#: templates/recruitment/candidate_update.html:116 msgid "Candidate Form" msgstr "" -#: templates/recruitment/candidate_update.html:135 msgid "Update Candidate" msgstr "" -#: templates/recruitment/training_create.html:119 msgid "Create New Training Material" msgstr "" -#: templates/recruitment/training_create.html:121 msgid "Upload a new document or guide for your team." msgstr "" -#: templates/recruitment/training_create.html:137 -#: templates/recruitment/training_update.html:131 msgid "Material Details" msgstr "" -#: templates/recruitment/training_create.html:158 msgid "Create Material" msgstr "" -#: templates/recruitment/training_list.html:135 -#: templates/recruitment/training_list.html:137 msgid "Add New Material" msgstr "" -#: templates/recruitment/training_list.html:223 msgid "No training materials found." msgstr "" -#: templates/recruitment/training_list.html:224 msgid "It looks like there are no materials yet. Start by adding one!" msgstr "" -#: templates/recruitment/training_list.html:228 msgid "Create Your First Material" msgstr "" -#: templates/recruitment/training_update.html:107 msgid "Update Training Material:" msgstr "" -#: templates/recruitment/training_update.html:109 msgid "Edit the details of this training document or guide." msgstr "" -#: templates/recruitment/training_update.html:117 msgid "View Material" msgstr "" -#: templates/recruitment/training_update.html:180 msgid "Update Material" msgstr "" -#: templates/recruitment/training_update.html:182 msgid "Are you sure you want to delete this material?" msgstr "" -#: templates/unfold/components/table.html:43 msgid "No data" msgstr "" -# Source Management msgid "Data Sources" msgstr "" @@ -1448,21 +1024,12 @@ msgstr "" msgid "View Details" msgstr "" -msgid "Edit" -msgstr "" - -msgid "Delete" -msgstr "" - msgid "First" msgstr "" msgid "Previous" msgstr "" -msgid "Next" -msgstr "" - msgid "Last" msgstr "" @@ -1475,9 +1042,6 @@ msgstr "" msgid "Network Configuration" msgstr "" -msgid "Settings" -msgstr "" - msgid "API Configuration" msgstr "" @@ -1487,63 +1051,33 @@ msgstr "" msgid "Generate secure API keys for external integrations" msgstr "" -msgid "Active" -msgstr "" - msgid "Inactive" msgstr "" msgid "Not generated" msgstr "" -msgid "Created By" -msgstr "" - msgid "Created At" msgstr "" msgid "Updated At" msgstr "" -msgid "IP Address" -msgstr "" - msgid "Trusted IPs" msgstr "" -msgid "Integration Version" -msgstr "" - -msgid "API Key" -msgstr "" - -msgid "API Secret" -msgstr "" - msgid "Recent Integration Logs" msgstr "" msgid "Time" msgstr "" -msgid "Action" -msgstr "" - -msgid "Endpoint" -msgstr "" - msgid "Method" msgstr "" -msgid "Status" -msgstr "" - msgid "Success" msgstr "" -msgid "Failed" -msgstr "" - msgid "No integration logs found" msgstr "" @@ -1571,12 +1105,6 @@ msgstr "" msgid "Any active integrations using this source will be disconnected." msgstr "" -msgid "Back to List" -msgstr "" - -msgid "Delete Source" -msgstr "" - msgid "Generate API Keys" msgstr "" @@ -1599,4 +1127,4 @@ msgid "Source created successfully." msgstr "" msgid "Source deleted successfully." -msgstr "" +msgstr "" \ No newline at end of file diff --git a/recruitment/__pycache__/admin.cpython-313.pyc b/recruitment/__pycache__/admin.cpython-313.pyc index 02337a2..efdeced 100644 Binary files a/recruitment/__pycache__/admin.cpython-313.pyc and b/recruitment/__pycache__/admin.cpython-313.pyc differ diff --git a/recruitment/__pycache__/forms.cpython-313.pyc b/recruitment/__pycache__/forms.cpython-313.pyc index 4a127ba..20fd6a9 100644 Binary files a/recruitment/__pycache__/forms.cpython-313.pyc and b/recruitment/__pycache__/forms.cpython-313.pyc differ diff --git a/recruitment/__pycache__/models.cpython-313.pyc b/recruitment/__pycache__/models.cpython-313.pyc index eac1470..aeceeb2 100644 Binary files a/recruitment/__pycache__/models.cpython-313.pyc and b/recruitment/__pycache__/models.cpython-313.pyc differ diff --git a/recruitment/__pycache__/serializers.cpython-313.pyc b/recruitment/__pycache__/serializers.cpython-313.pyc index 72958ae..7bef3a1 100644 Binary files a/recruitment/__pycache__/serializers.cpython-313.pyc and b/recruitment/__pycache__/serializers.cpython-313.pyc differ diff --git a/recruitment/__pycache__/signals.cpython-313.pyc b/recruitment/__pycache__/signals.cpython-313.pyc index 4c2eb87..f70295e 100644 Binary files a/recruitment/__pycache__/signals.cpython-313.pyc and b/recruitment/__pycache__/signals.cpython-313.pyc differ diff --git a/recruitment/__pycache__/urls.cpython-313.pyc b/recruitment/__pycache__/urls.cpython-313.pyc deleted file mode 100644 index a4bcbcd..0000000 Binary files a/recruitment/__pycache__/urls.cpython-313.pyc and /dev/null differ diff --git a/recruitment/__pycache__/utils.cpython-313.pyc b/recruitment/__pycache__/utils.cpython-313.pyc index 62e34b1..7fb574e 100644 Binary files a/recruitment/__pycache__/utils.cpython-313.pyc and b/recruitment/__pycache__/utils.cpython-313.pyc differ diff --git a/recruitment/__pycache__/views.cpython-313.pyc b/recruitment/__pycache__/views.cpython-313.pyc index a45f0cd..17e6e18 100644 Binary files a/recruitment/__pycache__/views.cpython-313.pyc and b/recruitment/__pycache__/views.cpython-313.pyc differ diff --git a/recruitment/__pycache__/views_frontend.cpython-313.pyc b/recruitment/__pycache__/views_frontend.cpython-313.pyc index b90a714..444d815 100644 Binary files a/recruitment/__pycache__/views_frontend.cpython-313.pyc and b/recruitment/__pycache__/views_frontend.cpython-313.pyc differ diff --git a/recruitment/admin.py b/recruitment/admin.py index cd2c8a1..245a14d 100644 --- a/recruitment/admin.py +++ b/recruitment/admin.py @@ -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) diff --git a/recruitment/decorators.py b/recruitment/decorators.py index b929bf2..06e68b1 100644 --- a/recruitment/decorators.py +++ b/recruitment/decorators.py @@ -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 \ No newline at end of file + 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) diff --git a/recruitment/forms.py b/recruitment/forms.py index 76bbfc9..67e3ee1 100644 --- a/recruitment/forms.py +++ b/recruitment/forms.py @@ -4,15 +4,32 @@ from django.forms.formsets import formset_factory from django.utils.translation import gettext_lazy as _ from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Submit, Row, Column, Field, Div -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.contrib.auth.forms import UserCreationForm + +User = get_user_model() import re from .models import ( - ZoomMeeting, Candidate,TrainingMaterial,JobPosting, - FormTemplate,InterviewSchedule,BreakTime,JobPostingImage, - Profile,MeetingComment,ScheduledInterview,Source,HiringAgency, - AgencyJobAssignment, AgencyAccessLink,Participants,OnsiteMeeting + ZoomMeeting, + Application, + TrainingMaterial, + JobPosting, + FormTemplate, + InterviewSchedule, + BreakTime, + JobPostingImage, + Profile, + MeetingComment, + ScheduledInterview, + Source, + HiringAgency, + AgencyJobAssignment, + AgencyAccessLink, + Participants, + Message, + Person,OnsiteMeeting ) + # from django_summernote.widgets import SummernoteWidget from django_ckeditor_5.widgets import CKEditor5Widget import secrets @@ -20,79 +37,84 @@ import string from django.core.exceptions import ValidationError from django.utils import timezone + def generate_api_key(length=32): """Generate a secure API key""" alphabet = string.ascii_letters + string.digits - return ''.join(secrets.choice(alphabet) for _ in range(length)) + return "".join(secrets.choice(alphabet) for _ in range(length)) + def generate_api_secret(length=64): """Generate a secure API secret""" - alphabet = string.ascii_letters + string.digits + '-._~' - return ''.join(secrets.choice(alphabet) for _ in range(length)) + alphabet = string.ascii_letters + string.digits + "-._~" + return "".join(secrets.choice(alphabet) for _ in range(length)) + class SourceForm(forms.ModelForm): """Simple form for creating and editing sources""" class Meta: model = Source - fields = [ - 'name', 'source_type', 'description', 'ip_address', 'is_active' - ] + fields = ["name", "source_type", "description", "ip_address", "is_active"] widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'e.g., ATS System, ERP Integration', - 'required': True - }), - 'source_type': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'e.g., ATS, ERP, API', - 'required': True - }), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Brief description of the source system' - }), - 'ip_address': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '192.168.1.100' - }), - 'is_active': forms.CheckboxInput(attrs={ - 'class': 'form-check-input' - }), + "name": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "e.g., ATS System, ERP Integration", + "required": True, + } + ), + "source_type": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "e.g., ATS, ERP, API", + "required": True, + } + ), + "description": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": "Brief description of the source system", + } + ), + "ip_address": forms.TextInput( + attrs={"class": "form-control", "placeholder": "192.168.1.100"} + ), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" self.helper.layout = Layout( - Field('name', css_class='form-control'), - Field('source_type', css_class='form-control'), - Field('ip_address', css_class='form-control'), - Field('is_active', css_class='form-check-input'), - Submit('submit', 'Save Source', css_class='btn btn-primary mt-3') + Field("name", css_class="form-control"), + Field("source_type", css_class="form-control"), + Field("ip_address", css_class="form-control"), + Field("is_active", css_class="form-check-input"), + Submit("submit", "Save Source", css_class="btn btn-primary mt-3"), ) def clean_name(self): """Ensure source name is unique""" - name = self.cleaned_data.get('name') + name = self.cleaned_data.get("name") if name: # Check for duplicates excluding current instance if editing instance = self.instance if not instance.pk: # Creating new instance if Source.objects.filter(name=name).exists(): - raise ValidationError('A source with this name already exists.') + raise ValidationError("A source with this name already exists.") else: # Editing existing instance if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): - raise ValidationError('A source with this name already exists.') + raise ValidationError("A source with this name already exists.") return name + class SourceAdvancedForm(forms.ModelForm): """Advanced form for creating and editing sources with API key generation""" @@ -100,118 +122,126 @@ class SourceAdvancedForm(forms.ModelForm): generate_keys = forms.CharField( widget=forms.HiddenInput(), required=False, - help_text="Set to 'true' to generate new API keys" + help_text="Set to 'true' to generate new API keys", ) # Display fields for generated keys (read-only) api_key_generated = forms.CharField( label="Generated API Key", required=False, - widget=forms.TextInput(attrs={'readonly': True, 'class': 'form-control'}) + widget=forms.TextInput(attrs={"readonly": True, "class": "form-control"}), ) api_secret_generated = forms.CharField( label="Generated API Secret", required=False, - widget=forms.TextInput(attrs={'readonly': True, 'class': 'form-control'}) + widget=forms.TextInput(attrs={"readonly": True, "class": "form-control"}), ) class Meta: model = Source fields = [ - 'name', 'source_type', 'description', 'ip_address', - 'trusted_ips', 'is_active', 'integration_version' + "name", + "source_type", + "description", + "ip_address", + "trusted_ips", + "is_active", + "integration_version", ] widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'e.g., ATS System, ERP Integration', - 'required': True - }), - 'source_type': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'e.g., ATS, ERP, API', - 'required': True - }), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Brief description of the source system' - }), - 'ip_address': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '192.168.1.100' - }), - 'trusted_ips': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 2, - 'placeholder': 'Comma-separated IP addresses (e.g., 192.168.1.100, 10.0.0.1)' - }), - 'integration_version': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'v1.0, v2.1' - }), + "name": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "e.g., ATS System, ERP Integration", + "required": True, + } + ), + "source_type": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "e.g., ATS, ERP, API", + "required": True, + } + ), + "description": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": "Brief description of the source system", + } + ), + "ip_address": forms.TextInput( + attrs={"class": "form-control", "placeholder": "192.168.1.100"} + ), + "trusted_ips": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 2, + "placeholder": "Comma-separated IP addresses (e.g., 192.168.1.100, 10.0.0.1)", + } + ), + "integration_version": forms.TextInput( + attrs={"class": "form-control", "placeholder": "v1.0, v2.1"} + ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" # Add generate keys button self.helper.layout = Layout( - Field('name', css_class='form-control'), - Field('source_type', css_class='form-control'), - Field('description', css_class='form-control'), - Field('ip_address', css_class='form-control'), - Field('trusted_ips', css_class='form-control'), - Field('integration_version', css_class='form-control'), - Field('is_active', css_class='form-check-input'), - + Field("name", css_class="form-control"), + Field("source_type", css_class="form-control"), + Field("description", css_class="form-control"), + Field("ip_address", css_class="form-control"), + Field("trusted_ips", css_class="form-control"), + Field("integration_version", css_class="form-control"), + Field("is_active", css_class="form-check-input"), # Hidden field for key generation trigger - Field('generate_keys', type='hidden'), - + Field("generate_keys", type="hidden"), # Display fields for generated keys - Field('api_key_generated', css_class='form-control'), - Field('api_secret_generated', css_class='form-control'), - - Submit('submit', 'Save Source', css_class='btn btn-primary mt-3') + Field("api_key_generated", css_class="form-control"), + Field("api_secret_generated", css_class="form-control"), + Submit("submit", "Save Source", css_class="btn btn-primary mt-3"), ) def clean_name(self): """Ensure source name is unique""" - name = self.cleaned_data.get('name') + name = self.cleaned_data.get("name") if name: # Check for duplicates excluding current instance if editing instance = self.instance if not instance.pk: # Creating new instance if Source.objects.filter(name=name).exists(): - raise ValidationError('A source with this name already exists.') + raise ValidationError("A source with this name already exists.") else: # Editing existing instance if Source.objects.filter(name=name).exclude(pk=instance.pk).exists(): - raise ValidationError('A source with this name already exists.') + raise ValidationError("A source with this name already exists.") return name def clean_trusted_ips(self): """Validate and format trusted IP addresses""" - trusted_ips = self.cleaned_data.get('trusted_ips') + trusted_ips = self.cleaned_data.get("trusted_ips") if trusted_ips: # Split by comma and strip whitespace - ips = [ip.strip() for ip in trusted_ips.split(',') if ip.strip()] + ips = [ip.strip() for ip in trusted_ips.split(",") if ip.strip()] # Validate each IP address for ip in ips: try: # Basic IP validation (can be enhanced) - if not (ip.replace('.', '').isdigit() and len(ip.split('.')) == 4): - raise ValidationError(f'Invalid IP address: {ip}') + if not (ip.replace(".", "").isdigit() and len(ip.split(".")) == 4): + raise ValidationError(f"Invalid IP address: {ip}") except Exception: - raise ValidationError(f'Invalid IP address: {ip}') + raise ValidationError(f"Invalid IP address: {ip}") - return ', '.join(ips) + return ", ".join(ips) return trusted_ips def clean(self): @@ -219,147 +249,203 @@ class SourceAdvancedForm(forms.ModelForm): cleaned_data = super().clean() # Check if we need to generate API keys - generate_keys = cleaned_data.get('generate_keys') + generate_keys = cleaned_data.get("generate_keys") - if generate_keys == 'true': + if generate_keys == "true": # Generate new API key and secret - cleaned_data['api_key'] = generate_api_key() - cleaned_data['api_secret'] = generate_api_secret() + cleaned_data["api_key"] = generate_api_key() + cleaned_data["api_secret"] = generate_api_secret() # Set display fields for the frontend - cleaned_data['api_key_generated'] = cleaned_data['api_key'] - cleaned_data['api_secret_generated'] = cleaned_data['api_secret'] + cleaned_data["api_key_generated"] = cleaned_data["api_key"] + cleaned_data["api_secret_generated"] = cleaned_data["api_secret"] return cleaned_data -class CandidateForm(forms.ModelForm): + +class PersonForm(forms.ModelForm): class Meta: - model = Candidate - fields = ['job', 'first_name', 'last_name', 'phone', 'email','hiring_source','hiring_agency', 'resume',] + model = Person + fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","address","gender"] + widgets = { + "first_name": forms.TextInput(attrs={'class': 'form-control'}), + "middle_name": forms.TextInput(attrs={'class': 'form-control'}), + "last_name": forms.TextInput(attrs={'class': 'form-control'}), + "email": forms.EmailInput(attrs={'class': 'form-control'}), + "phone": forms.TextInput(attrs={'class': 'form-control'}), + "gender": forms.Select(attrs={'class': 'form-control'}), + "date_of_birth": forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}), + "nationality": forms.Select(attrs={'class': 'form-control select2'}), + "address": forms.Textarea(attrs={'class': 'form-control', 'rows': 3}), + } +class ApplicationForm(forms.ModelForm): + + class Meta: + model = Application + fields = [ + 'person', + "job", + "hiring_source", + "hiring_agency", + "resume", + ] labels = { - 'first_name': _('First Name'), - 'last_name': _('Last Name'), - 'phone': _('Phone'), - 'email': _('Email'), - 'resume': _('Resume'), - 'hiring_source': _('Hiring Type'), - 'hiring_agency': _('Hiring Agency'), + "resume": _("Resume"), + "hiring_source": _("Hiring Type"), + "hiring_agency": _("Hiring Agency"), } widgets = { - 'first_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter first name')}), - 'last_name': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter last name')}), - 'phone': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter phone number')}), - 'email': forms.EmailInput(attrs={'class': 'form-control', 'placeholder': _('Enter email')}), - 'stage': forms.Select(attrs={'class': 'form-select'}), - 'hiring_source': forms.Select(attrs={'class': 'form-select'}), - 'hiring_agency': forms.Select(attrs={'class': 'form-select'}), + "hiring_source": forms.Select(attrs={"class": "form-select"}), + "hiring_agency": forms.Select(attrs={"class": "form-select"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" # Make job field read-only if it's being pre-populated - job_value = self.initial.get('job') + job_value = self.initial.get("job") if job_value: - self.fields['job'].widget.attrs['readonly'] = True + self.fields["job"].widget.attrs["readonly"] = True self.helper.layout = Layout( - Field('job', css_class='form-control'), - Field('first_name', css_class='form-control'), - Field('last_name', css_class='form-control'), - Field('phone', css_class='form-control'), - Field('email', css_class='form-control'), - Field('stage', css_class='form-control'), - Field('hiring_source', css_class='form-control'), - Field('hiring_agency', css_class='form-control'), - Field('resume', css_class='form-control'), - Submit('submit', _('Submit'), css_class='btn btn-primary') - + Field("job", css_class="form-control"), + Field("hiring_source", css_class="form-control"), + Field("hiring_agency", css_class="form-control"), + Field("resume", css_class="form-control"), + Submit("submit", _("Submit"), css_class="btn btn-primary"), ) -class CandidateStageForm(forms.ModelForm): + # def save(self, commit=True): + # """Override save to handle person creation/update""" + # instance = super().save(commit=False) + + # # Get or create person + # if instance.person: + # person = instance.person + # else: + # # Create new person + # from .models import Person + # person = Person() + + # # Update person fields + # person.first_name = self.cleaned_data['first_name'] + # person.last_name = self.cleaned_data['last_name'] + # person.email = self.cleaned_data['email'] + # person.phone = self.cleaned_data['phone'] + + # if commit: + # person.save() + # instance.person = person + # instance.save() + + # return instance + + +class ApplicationStageForm(forms.ModelForm): """Form specifically for updating candidate stage with validation""" class Meta: - model = Candidate - fields = ['stage'] + model = Application + fields = ["stage"] labels = { - 'stage': _('New Application Stage'), + "stage": _("New Application Stage"), } widgets = { - 'stage': forms.Select(attrs={'class': 'form-select'}), + "stage": forms.Select(attrs={"class": "form-select"}), } + class ZoomMeetingForm(forms.ModelForm): class Meta: model = ZoomMeeting - fields = ['topic', 'start_time', 'duration'] + fields = ["topic", "start_time", "duration"] labels = { - 'topic': _('Topic'), - 'start_time': _('Start Time'), - 'duration': _('Duration'), + "topic": _("Topic"), + "start_time": _("Start Time"), + "duration": _("Duration"), } widgets = { - 'topic': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter meeting topic'),}), - 'start_time': forms.DateTimeInput(attrs={'class': 'form-control','type': 'datetime-local'}), - 'duration': forms.NumberInput(attrs={'class': 'form-control','min': 1, 'placeholder': _('60')}), + "topic": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": _("Enter meeting topic"), + } + ), + "start_time": forms.DateTimeInput( + attrs={"class": "form-control", "type": "datetime-local"} + ), + "duration": forms.NumberInput( + attrs={"class": "form-control", "min": 1, "placeholder": _("60")} + ), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" self.helper.layout = Layout( - Field('topic', css_class='form-control'), - Field('start_time', css_class='form-control'), - Field('duration', css_class='form-control'), - Submit('submit', _('Create Meeting'), css_class='btn btn-primary') + Field("topic", css_class="form-control"), + Field("start_time", css_class="form-control"), + Field("duration", css_class="form-control"), + Submit("submit", _("Create Meeting"), css_class="btn btn-primary"), ) + class TrainingMaterialForm(forms.ModelForm): class Meta: model = TrainingMaterial - fields = ['title', 'content', 'video_link', 'file'] + fields = ["title", "content", "video_link", "file"] labels = { - 'title': _('Title'), - 'content': _('Content'), - 'video_link': _('Video Link'), - 'file': _('File'), + "title": _("Title"), + "content": _("Content"), + "video_link": _("Video Link"), + "file": _("File"), } widgets = { - 'title': forms.TextInput(attrs={'class': 'form-control', 'placeholder': _('Enter material title')}), - 'content': CKEditor5Widget(attrs={'placeholder': _('Enter material content')}), - 'video_link': forms.URLInput(attrs={'class': 'form-control', 'placeholder': _('https://www.youtube.com/watch?v=...')}), - 'file': forms.FileInput(attrs={'class': 'form-control'}), + "title": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": _("Enter material title"), + } + ), + "content": CKEditor5Widget( + attrs={"placeholder": _("Enter material content")} + ), + "video_link": forms.URLInput( + attrs={ + "class": "form-control", + "placeholder": _("https://www.youtube.com/watch?v=..."), + } + ), + "file": forms.FileInput(attrs={"class": "form-control"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'g-3' + self.helper.form_method = "post" + self.helper.form_class = "g-3" self.helper.layout = Layout( - 'title', - 'content', + "title", + "content", Row( - Column('video_link', css_class='col-md-6'), - Column('file', css_class='col-md-6'), - css_class='g-3 mb-4' + Column("video_link", css_class="col-md-6"), + Column("file", css_class="col-md-6"), + css_class="g-3 mb-4", ), Div( - Submit('submit', _('Create Material'), - css_class='btn btn-main-action'), - css_class='col-12 mt-4' - ) + Submit("submit", _("Create Material"), css_class="btn btn-main-action"), + css_class="col-12 mt-4", + ), ) @@ -369,117 +455,116 @@ class JobPostingForm(forms.ModelForm): class Meta: model = JobPosting fields = [ - 'title', 'department', 'job_type', 'workplace_type', - 'location_city', 'location_state', 'location_country', - 'description', 'qualifications', 'salary_range', 'benefits', - 'application_deadline', 'application_instructions', - 'position_number', 'reporting_to', - 'open_positions', 'hash_tags', 'max_applications' + "title", + "department", + "job_type", + "workplace_type", + "location_city", + "location_state", + "location_country", + "description", + "qualifications", + "salary_range", + "benefits", + "application_deadline", + "application_instructions", + "position_number", + "reporting_to", + "open_positions", + "hash_tags", + "max_applications", ] widgets = { # Basic Information - 'title': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Assistant Professor of Computer Science', - 'required': True - }), - 'department': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Computer Science, Human Resources, etc.' - }), - 'job_type': forms.Select(attrs={ - 'class': 'form-select', - 'required': True - }), - 'workplace_type': forms.Select(attrs={ - 'class': 'form-select', - 'required': True - }), - + "title": forms.TextInput( + attrs={"class": "form-control", "placeholder": "", "required": True} + ), + "department": forms.TextInput( + attrs={"class": "form-control", "placeholder": ""} + ), + "job_type": forms.Select(attrs={"class": "form-select", "required": True}), + "workplace_type": forms.Select( + attrs={"class": "form-select", "required": True} + ), # Location - 'location_city': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Boston' - }), - 'location_state': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'MA' - }), - 'location_country': forms.TextInput(attrs={ - 'class': 'form-control', - 'value': 'United States' - }), - - 'salary_range': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '$60,000 - $80,000' - }), - + "location_city": forms.TextInput( + attrs={"class": "form-control", "placeholder": "Boston"} + ), + "location_state": forms.TextInput( + attrs={"class": "form-control", "placeholder": "MA"} + ), + "location_country": forms.TextInput( + attrs={"class": "form-control", "value": "United States"} + ), + "salary_range": forms.TextInput( + attrs={"class": "form-control", "placeholder": "$60,000 - $80,000"} + ), # Application Information # 'application_url': forms.URLInput(attrs={ # 'class': 'form-control', # 'placeholder': 'https://university.edu/careers/job123', # 'required': True # }), - - 'application_deadline': forms.DateInput(attrs={ - 'class': 'form-control', - 'type': 'date', - 'required': True - }), - - 'open_positions': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 1, - 'placeholder': 'Number of open positions' - }), - 'hash_tags': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '#hiring,#jobopening', - # 'validators':validate_hash_tags, # Assuming this is available - }), - + "application_deadline": forms.DateInput( + attrs={"class": "form-control", "type": "date", "required": True} + ), + "open_positions": forms.NumberInput( + attrs={ + "class": "form-control", + "min": 1, + "placeholder": "Number of open positions", + } + ), + "hash_tags": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "#hiring,#jobopening", + # 'validators':validate_hash_tags, # Assuming this is available + } + ), # Internal Information - 'position_number': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'UNIV-2025-001' - }), - 'reporting_to': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Department Chair, Director, etc.' - }), - - 'max_applications': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 1, - 'placeholder': 'Maximum number of applicants' - }), + "position_number": forms.TextInput( + attrs={"class": "form-control", "placeholder": "UNIV-2025-001"} + ), + "reporting_to": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Department Chair, Director, etc.", + } + ), + "max_applications": forms.NumberInput( + attrs={ + "class": "form-control", + "min": 1, + "placeholder": "Maximum number of applicants", + } + ), } def __init__(self, *args, **kwargs): - # Now call the parent __init__ with remaining args super().__init__(*args, **kwargs) if not self.instance.pk: # Creating new job posting # self.fields['status'].initial = 'Draft' - self.fields['location_city'].initial = 'Riyadh' - self.fields['location_state'].initial = 'Riyadh Province' - self.fields['location_country'].initial = 'Saudi Arabia' + self.fields["location_city"].initial = "Riyadh" + self.fields["location_state"].initial = "Riyadh Province" + self.fields["location_country"].initial = "Saudi Arabia" def clean_hash_tags(self): - hash_tags = self.cleaned_data.get('hash_tags') + hash_tags = self.cleaned_data.get("hash_tags") if hash_tags: - tags = [tag.strip() for tag in hash_tags.split(',') if tag.strip()] + tags = [tag.strip() for tag in hash_tags.split(",") if tag.strip()] for tag in tags: - if not tag.startswith('#'): + if not tag.startswith("#"): raise forms.ValidationError( - "Each hashtag must start with '#' symbol and must be comma(,) sepearted.") - return ','.join(tags) + "Each hashtag must start with '#' symbol and must be comma(,) sepearted." + ) + return ",".join(tags) return hash_tags # Allow blank def clean_title(self): - title = self.cleaned_data.get('title') + title = self.cleaned_data.get("title") if not title or len(title.strip()) < 3: raise forms.ValidationError("Job title must be at least 3 characters long.") if len(title) > 200: @@ -487,166 +572,211 @@ class JobPostingForm(forms.ModelForm): return title.strip() def clean_description(self): - description = self.cleaned_data.get('description') + description = self.cleaned_data.get("description") if not description or len(description.strip()) < 20: - raise forms.ValidationError("Job description must be at least 20 characters long.") + raise forms.ValidationError( + "Job description must be at least 20 characters long." + ) return description.strip() # to remove leading/trailing whitespace def clean_application_url(self): - url = self.cleaned_data.get('application_url') + url = self.cleaned_data.get("application_url") if url: validator = URLValidator() try: validator(url) except forms.ValidationError: - raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)') + raise forms.ValidationError( + "Please enter a valid URL (e.g., https://example.com)" + ) return url + class JobPostingImageForm(forms.ModelForm): class Meta: - model=JobPostingImage - fields=['post_image'] + model = JobPostingImage + fields = ["post_image"] + class FormTemplateForm(forms.ModelForm): """Form for creating form templates""" + class Meta: model = FormTemplate - fields = ['job','name', 'description', 'is_active'] + fields = ["job", "name", "description", "is_active"] labels = { - 'job': _('Job'), - 'name': _('Template Name'), - 'description': _('Description'), - 'is_active': _('Active'), + "job": _("Job"), + "name": _("Template Name"), + "description": _("Description"), + "is_active": _("Active"), } widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': _('Enter template name'), - 'required': True - }), - 'description': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': _('Enter template description (optional)') - }), - 'is_active': forms.CheckboxInput(attrs={ - 'class': 'form-check-input' - }) + "name": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": _("Enter template name"), + "required": True, + } + ), + "description": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": _("Enter template description (optional)"), + } + ), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" self.helper.layout = Layout( - Field('job', css_class='form-control'), - Field('name', css_class='form-control'), - Field('description', css_class='form-control'), - Field('is_active', css_class='form-check-input'), - Submit('submit', _('Create Template'), css_class='btn btn-primary mt-3') + Field("job", css_class="form-control"), + Field("name", css_class="form-control"), + Field("description", css_class="form-control"), + Field("is_active", css_class="form-check-input"), + Submit("submit", _("Create Template"), css_class="btn btn-primary mt-3"), ) + class BreakTimeForm(forms.Form): """ A simple Form used for the BreakTimeFormSet. It is not a ModelForm because the data is stored directly in InterviewSchedule's JSONField, not in a separate BreakTime model instance. """ + start_time = forms.TimeField( - widget=forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), - label="Start Time" + widget=forms.TimeInput(attrs={"type": "time", "class": "form-control"}), + label="Start Time", ) end_time = forms.TimeField( - widget=forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), - label="End Time" + widget=forms.TimeInput(attrs={"type": "time", "class": "form-control"}), + label="End Time", ) + BreakTimeFormSet = formset_factory(BreakTimeForm, extra=1, can_delete=True) + class InterviewScheduleForm(forms.ModelForm): - candidates = forms.ModelMultipleChoiceField( - queryset=Candidate.objects.none(), + applications = forms.ModelMultipleChoiceField( + queryset=Application.objects.none(), widget=forms.CheckboxSelectMultiple, - required=True + required=True, ) working_days = forms.MultipleChoiceField( choices=[ - (0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), - (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday'), + (0, "Monday"), + (1, "Tuesday"), + (2, "Wednesday"), + (3, "Thursday"), + (4, "Friday"), + (5, "Saturday"), + (6, "Sunday"), ], widget=forms.CheckboxSelectMultiple, - required=True + required=True, ) class Meta: model = InterviewSchedule fields = [ - 'candidates', 'interview_type', 'start_date', 'end_date', 'working_days', - 'start_time', 'end_time', 'interview_duration', 'buffer_time', - 'break_start_time', 'break_end_time' + "applications", + "start_date", + "end_date", + "working_days", + "start_time", + "end_time", + "interview_duration", + "buffer_time", + "break_start_time", + "break_end_time", + "interview_type" ] widgets = { 'interview_type': forms.Select(attrs={'class': 'form-control'}), - 'start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), - 'end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), - 'start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), - 'end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), - 'interview_duration': forms.NumberInput(attrs={'class': 'form-control'}), - 'buffer_time': forms.NumberInput(attrs={'class': 'form-control'}), - 'break_start_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), - 'break_end_time': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}), + "start_date": forms.DateInput( + attrs={"type": "date", "class": "form-control"} + ), + "end_date": forms.DateInput( + attrs={"type": "date", "class": "form-control"} + ), + "start_time": forms.TimeInput( + attrs={"type": "time", "class": "form-control"} + ), + "end_time": forms.TimeInput( + attrs={"type": "time", "class": "form-control"} + ), + "interview_duration": forms.NumberInput(attrs={"class": "form-control"}), + "buffer_time": forms.NumberInput(attrs={"class": "form-control"}), + "break_start_time": forms.TimeInput( + attrs={"type": "time", "class": "form-control"} + ), + "break_end_time": forms.TimeInput( + attrs={"type": "time", "class": "form-control"} + ), } def __init__(self, slug, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields['candidates'].queryset = Candidate.objects.filter( - job__slug=slug, - stage='Interview' + self.fields["applications"].queryset = Application.objects.filter( + job__slug=slug, stage="Interview" ) def clean_working_days(self): - working_days = self.cleaned_data.get('working_days') + working_days = self.cleaned_data.get("working_days") return [int(day) for day in working_days] + class MeetingCommentForm(forms.ModelForm): """Form for creating and editing meeting comments""" + class Meta: model = MeetingComment - fields = ['content'] + fields = ["content"] widgets = { - 'content': CKEditor5Widget( - attrs={'class': 'form-control', 'placeholder': _('Enter your comment or note')}, - config_name='extends' + "content": CKEditor5Widget( + attrs={ + "class": "form-control", + "placeholder": _("Enter your comment or note"), + }, + config_name="extends", ), } labels = { - 'content': _('Comment'), + "content": _("Comment"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" self.helper.layout = Layout( - Field('content', css_class='form-control'), - Submit('submit', _('Add Comment'), css_class='btn btn-primary mt-3') + Field("content", css_class="form-control"), + Submit("submit", _("Add Comment"), css_class="btn btn-primary mt-3"), ) + class InterviewForm(forms.ModelForm): class Meta: model = ScheduledInterview - fields = ['job','candidate'] + fields = ["job", "application"] + class ProfileImageUploadForm(forms.ModelForm): class Meta: - model=Profile - fields=['profile_image'] + model = Profile + fields = ["profile_image"] + class StaffUserCreationForm(UserCreationForm): email = forms.EmailField(required=True) @@ -665,10 +795,10 @@ class StaffUserCreationForm(UserCreationForm): def generate_username(self, email): """Generate a valid, unique username from email.""" - prefix = email.split('@')[0].lower() - username = re.sub(r'[^a-z0-9._]', '', prefix) + prefix = email.split("@")[0].lower() + username = re.sub(r"[^a-z0-9._]", "", prefix) if not username: - username = 'user' + username = "user" base = username counter = 1 while User.objects.filter(username=username).exists(): @@ -687,173 +817,200 @@ class StaffUserCreationForm(UserCreationForm): user.save() return user + class ToggleAccountForm(forms.Form): pass + class JobPostingCancelReasonForm(forms.ModelForm): class Meta: model = JobPosting - fields = ['cancel_reason'] + fields = ["cancel_reason"] + class JobPostingStatusForm(forms.ModelForm): class Meta: model = JobPosting - fields = ['status'] + fields = ["status"] widgets = { - 'status': forms.Select(attrs={'class': 'form-select'}), + "status": forms.Select(attrs={"class": "form-select"}), } + + class LinkedPostContentForm(forms.ModelForm): class Meta: model = JobPosting - fields = ['linkedin_post_formated_data'] + fields = ["linkedin_post_formated_data"] + class FormTemplateIsActiveForm(forms.ModelForm): class Meta: model = FormTemplate - fields = ['is_active'] + fields = ["is_active"] + class CandidateExamDateForm(forms.ModelForm): class Meta: - model = Candidate - fields = ['exam_date'] + model = Application + fields = ["exam_date"] widgets = { - 'exam_date': forms.DateTimeInput(attrs={'type': 'datetime-local', 'class': 'form-control'}), + "exam_date": forms.DateTimeInput( + attrs={"type": "datetime-local", "class": "form-control"} + ), } + class HiringAgencyForm(forms.ModelForm): """Form for creating and editing hiring agencies""" class Meta: model = HiringAgency fields = [ - 'name', 'contact_person', 'email', 'phone', - 'website', 'country', 'address', 'notes' + "name", + "contact_person", + "email", + "phone", + "website", + "country", + "address", + "notes", ] widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter agency name', - 'required': True - }), - 'contact_person': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter contact person name' - }), - 'email': forms.EmailInput(attrs={ - 'class': 'form-control', - 'placeholder': 'agency@example.com' - }), - 'phone': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '+966 50 123 4567' - }), - 'website': forms.URLInput(attrs={ - 'class': 'form-control', - 'placeholder': 'https://www.agency.com' - }), - 'country': forms.Select(attrs={ - 'class': 'form-select' - }), - 'address': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Enter agency address' - }), - 'notes': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Internal notes about the agency' - }), + "name": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Enter agency name", + "required": True, + } + ), + "contact_person": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Enter contact person name", + } + ), + "email": forms.EmailInput( + attrs={"class": "form-control", "placeholder": "agency@example.com"} + ), + "phone": forms.TextInput( + attrs={"class": "form-control", "placeholder": "+966 50 123 4567"} + ), + "website": forms.URLInput( + attrs={"class": "form-control", "placeholder": "https://www.agency.com"} + ), + "country": forms.Select(attrs={"class": "form-select"}), + "address": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": "Enter agency address", + } + ), + "notes": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": "Internal notes about the agency", + } + ), } labels = { - 'name': _('Agency Name'), - 'contact_person': _('Contact Person'), - 'email': _('Email Address'), - 'phone': _('Phone Number'), - 'website': _('Website'), - 'country': _('Country'), - 'address': _('Address'), - 'notes': _('Internal Notes'), + "name": _("Agency Name"), + "contact_person": _("Contact Person"), + "email": _("Email Address"), + "phone": _("Phone Number"), + "website": _("Website"), + "country": _("Country"), + "address": _("Address"), + "notes": _("Internal Notes"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" self.helper.layout = Layout( - Field('name', css_class='form-control'), - Field('contact_person', css_class='form-control'), + Field("name", css_class="form-control"), + Field("contact_person", css_class="form-control"), Row( - Column('email', css_class='col-md-6'), - Column('phone', css_class='col-md-6'), - css_class='g-3 mb-3' + Column("email", css_class="col-md-6"), + Column("phone", css_class="col-md-6"), + css_class="g-3 mb-3", ), - Field('website', css_class='form-control'), - Field('country', css_class='form-control'), - Field('address', css_class='form-control'), - Field('notes', css_class='form-control'), + Field("website", css_class="form-control"), + Field("country", css_class="form-control"), + Field("address", css_class="form-control"), + Field("notes", css_class="form-control"), Div( - Submit('submit', _('Save Agency'), css_class='btn btn-main-action'), - css_class='col-12 mt-4' - ) + Submit("submit", _("Save Agency"), css_class="btn btn-main-action"), + css_class="col-12 mt-4", + ), ) def clean_name(self): """Ensure agency name is unique""" - name = self.cleaned_data.get('name') + name = self.cleaned_data.get("name") if name: instance = self.instance if not instance.pk: # Creating new instance if HiringAgency.objects.filter(name=name).exists(): - raise ValidationError('An agency with this name already exists.') + raise ValidationError("An agency with this name already exists.") else: # Editing existing instance - if HiringAgency.objects.filter(name=name).exclude(pk=instance.pk).exists(): - raise ValidationError('An agency with this name already exists.') + if ( + HiringAgency.objects.filter(name=name) + .exclude(pk=instance.pk) + .exists() + ): + raise ValidationError("An agency with this name already exists.") return name.strip() def clean_email(self): """Validate email format and uniqueness""" - email = self.cleaned_data.get('email') + email = self.cleaned_data.get("email") if email: # Check email format - if not '@' in email or '.' not in email.split('@')[1]: - raise ValidationError('Please enter a valid email address.') + if not "@" in email or "." not in email.split("@")[1]: + raise ValidationError("Please enter a valid email address.") # Check uniqueness (optional - remove if multiple agencies can have same email) instance = self.instance if not instance.pk: # Creating new instance if HiringAgency.objects.filter(email=email).exists(): - raise ValidationError('An agency with this email already exists.') + raise ValidationError("An agency with this email already exists.") else: # Editing existing instance - if HiringAgency.objects.filter(email=email).exclude(pk=instance.pk).exists(): - raise ValidationError('An agency with this email already exists.') + if ( + HiringAgency.objects.filter(email=email) + .exclude(pk=instance.pk) + .exists() + ): + raise ValidationError("An agency with this email already exists.") return email.lower().strip() if email else email def clean_phone(self): """Validate phone number format""" - phone = self.cleaned_data.get('phone') + phone = self.cleaned_data.get("phone") if phone: # Remove common formatting characters - clean_phone = ''.join(c for c in phone if c.isdigit() or c in '+') + clean_phone = "".join(c for c in phone if c.isdigit() or c in "+") if len(clean_phone) < 10: - raise ValidationError('Phone number must be at least 10 digits long.') + raise ValidationError("Phone number must be at least 10 digits long.") return phone.strip() if phone else phone def clean_website(self): """Validate website URL""" - website = self.cleaned_data.get('website') + website = self.cleaned_data.get("website") if website: - if not website.startswith(('http://', 'https://')): - website = 'https://' + website + if not website.startswith(("http://", "https://")): + website = "https://" + website validator = URLValidator() try: validator(website) except ValidationError: - raise ValidationError('Please enter a valid website URL.') + raise ValidationError("Please enter a valid website URL.") return website @@ -862,105 +1019,108 @@ class AgencyJobAssignmentForm(forms.ModelForm): class Meta: model = AgencyJobAssignment - fields = [ - 'agency', 'job', 'max_candidates', 'deadline_date','admin_notes' - ] + fields = ["agency", "job", "max_candidates", "deadline_date", "admin_notes"] widgets = { - 'agency': forms.Select(attrs={'class': 'form-select'}), - 'job': forms.Select(attrs={'class': 'form-select'}), - 'max_candidates': forms.NumberInput(attrs={ - 'class': 'form-control', - 'min': 1, - 'placeholder': 'Maximum number of candidates' - }), - 'deadline_date': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), - 'status': forms.Select(attrs={'class': 'form-select'}), - 'admin_notes': forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 3, - 'placeholder': 'Internal notes about this assignment' - }), + "agency": forms.Select(attrs={"class": "form-select"}), + "job": forms.Select(attrs={"class": "form-select"}), + "max_candidates": forms.NumberInput( + attrs={ + "class": "form-control", + "min": 1, + "placeholder": "Maximum number of candidates", + } + ), + "deadline_date": forms.DateTimeInput( + attrs={"class": "form-control", "type": "datetime-local"} + ), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), + "status": forms.Select(attrs={"class": "form-select"}), + "admin_notes": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 3, + "placeholder": "Internal notes about this assignment", + } + ), } labels = { - 'agency': _('Agency'), - 'job': _('Job Posting'), - 'max_candidates': _('Maximum Candidates'), - 'deadline_date': _('Deadline Date'), - 'is_active': _('Is Active'), - 'status': _('Status'), - 'admin_notes': _('Admin Notes'), + "agency": _("Agency"), + "job": _("Job Posting"), + "max_candidates": _("Maximum Candidates"), + "deadline_date": _("Deadline Date"), + "is_active": _("Is Active"), + "status": _("Status"), + "admin_notes": _("Admin Notes"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" # Filter jobs to only show active jobs - self.fields['job'].queryset = JobPosting.objects.filter( - status='ACTIVE' - ).order_by('-created_at') + self.fields["job"].queryset = JobPosting.objects.filter( + status="ACTIVE" + ).order_by("-created_at") self.helper.layout = Layout( Row( - Column('agency', css_class='col-md-6'), - Column('job', css_class='col-md-6'), - css_class='g-3 mb-3' + Column("agency", css_class="col-md-6"), + Column("job", css_class="col-md-6"), + css_class="g-3 mb-3", ), Row( - Column('max_candidates', css_class='col-md-6'), - Column('deadline_date', css_class='col-md-6'), - css_class='g-3 mb-3' + Column("max_candidates", css_class="col-md-6"), + Column("deadline_date", css_class="col-md-6"), + css_class="g-3 mb-3", ), Row( - Column('is_active', css_class='col-md-6'), - Column('status', css_class='col-md-6'), - css_class='g-3 mb-3' + Column("is_active", css_class="col-md-6"), + Column("status", css_class="col-md-6"), + css_class="g-3 mb-3", ), - Field('admin_notes', css_class='form-control'), + Field("admin_notes", css_class="form-control"), Div( - Submit('submit', _('Save Assignment'), css_class='btn btn-main-action'), - css_class='col-12 mt-4' - ) + Submit("submit", _("Save Assignment"), css_class="btn btn-main-action"), + css_class="col-12 mt-4", + ), ) def clean_deadline_date(self): """Validate deadline date is in the future""" - deadline_date = self.cleaned_data.get('deadline_date') + deadline_date = self.cleaned_data.get("deadline_date") if deadline_date and deadline_date <= timezone.now(): - raise ValidationError('Deadline date must be in the future.') + raise ValidationError("Deadline date must be in the future.") return deadline_date def clean_max_candidates(self): """Validate maximum candidates is positive""" - max_candidates = self.cleaned_data.get('max_candidates') + max_candidates = self.cleaned_data.get("max_candidates") if max_candidates and max_candidates <= 0: - raise ValidationError('Maximum candidates must be greater than 0.') + raise ValidationError("Maximum candidates must be greater than 0.") return max_candidates def clean(self): """Check for duplicate assignments""" cleaned_data = super().clean() - agency = cleaned_data.get('agency') - job = cleaned_data.get('job') + agency = cleaned_data.get("agency") + job = cleaned_data.get("job") if agency and job: # Check if this assignment already exists - existing = AgencyJobAssignment.objects.filter( - agency=agency, job=job - ).exclude(pk=self.instance.pk).first() + existing = ( + AgencyJobAssignment.objects.filter(agency=agency, job=job) + .exclude(pk=self.instance.pk) + .first() + ) if existing: raise ValidationError( - f'This job is already assigned to {agency.name}. ' - f'Current status: {existing.get_status_display()}' + f"This job is already assigned to {agency.name}. " + f"Current status: {existing.get_status_display()}" ) return cleaned_data @@ -971,171 +1131,207 @@ class AgencyAccessLinkForm(forms.ModelForm): class Meta: model = AgencyAccessLink - fields = [ - 'assignment', 'expires_at', 'is_active' - ] + fields = ["assignment", "expires_at", "is_active"] widgets = { - 'assignment': forms.Select(attrs={'class': 'form-select'}), - 'expires_at': forms.DateTimeInput(attrs={ - 'class': 'form-control', - 'type': 'datetime-local' - }), - 'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + "assignment": forms.Select(attrs={"class": "form-select"}), + "expires_at": forms.DateTimeInput( + attrs={"class": "form-control", "type": "datetime-local"} + ), + "is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}), } labels = { - 'assignment': _('Assignment'), - 'expires_at': _('Expires At'), - 'is_active': _('Is Active'), + "assignment": _("Assignment"), + "expires_at": _("Expires At"), + "is_active": _("Is Active"), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'form-horizontal' - self.helper.label_class = 'col-md-3' - self.helper.field_class = 'col-md-9' + self.helper.form_method = "post" + self.helper.form_class = "form-horizontal" + self.helper.label_class = "col-md-3" + self.helper.field_class = "col-md-9" # Filter assignments to only show active ones without existing links - self.fields['assignment'].queryset = AgencyJobAssignment.objects.filter( - is_active=True, - status='ACTIVE' - ).exclude( - access_link__isnull=False - ).order_by('-created_at') + self.fields["assignment"].queryset = ( + AgencyJobAssignment.objects.filter(is_active=True, status="ACTIVE") + .exclude(access_link__isnull=False) + .order_by("-created_at") + ) self.helper.layout = Layout( - Field('assignment', css_class='form-control'), - Field('expires_at', css_class='form-control'), - Field('is_active', css_class='form-check-input'), + Field("assignment", css_class="form-control"), + Field("expires_at", css_class="form-control"), + Field("is_active", css_class="form-check-input"), Div( - Submit('submit', _('Create Access Link'), css_class='btn btn-main-action'), - css_class='col-12 mt-4' - ) + Submit( + "submit", _("Create Access Link"), css_class="btn btn-main-action" + ), + css_class="col-12 mt-4", + ), ) def clean_expires_at(self): """Validate expiration date is in the future""" - expires_at = self.cleaned_data.get('expires_at') + expires_at = self.cleaned_data.get("expires_at") if expires_at and expires_at <= timezone.now(): - raise ValidationError('Expiration date must be in the future.') + raise ValidationError("Expiration date must be in the future.") return expires_at # Agency messaging forms removed - AgencyMessage model has been deleted -class AgencyCandidateSubmissionForm(forms.ModelForm): +class AgencyApplicationSubmissionForm(forms.ModelForm): """Form for agencies to submit candidates (simplified - resume + basic info)""" + # Person fields for creating/updating person + # first_name = forms.CharField( + # max_length=255, + # widget=forms.TextInput(attrs={ + # "class": "form-control", + # "placeholder": "First Name", + # "required": True, + # }), + # label=_("First Name") + # ) + # last_name = forms.CharField( + # max_length=255, + # widget=forms.TextInput(attrs={ + # "class": "form-control", + # "placeholder": "Last Name", + # "required": True, + # }), + # label=_("Last Name") + # ) + # email = forms.EmailField( + # widget=forms.EmailInput(attrs={ + # "class": "form-control", + # "placeholder": "email@example.com", + # "required": True, + # }), + # label=_("Email Address") + # ) + # phone = forms.CharField( + # max_length=20, + # widget=forms.TextInput(attrs={ + # "class": "form-control", + # "placeholder": "+966 50 123 4567", + # "required": True, + # }), + # label=_("Phone Number") + # ) + class Meta: - model = Candidate - fields = [ - 'first_name', 'last_name', 'email', 'phone', 'resume' - ] + model = Application + fields = ["person","resume"] widgets = { - 'first_name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'First Name', - 'required': True - }), - 'last_name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Last Name', - 'required': True - }), - 'email': forms.EmailInput(attrs={ - 'class': 'form-control', - 'placeholder': 'email@example.com', - 'required': True - }), - 'phone': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': '+966 50 123 4567', - 'required': True - }), - 'resume': forms.FileInput(attrs={ - 'class': 'form-control', - 'accept': '.pdf,.doc,.docx', - 'required': True - }), + "resume": forms.FileInput( + attrs={ + "class": "form-control", + "accept": ".pdf,.doc,.docx", + "required": True, + } + ), } labels = { - 'first_name': _('First Name'), - 'last_name': _('Last Name'), - 'email': _('Email Address'), - 'phone': _('Phone Number'), - 'resume': _('Resume'), + "resume": _("Resume"), } def __init__(self, assignment, *args, **kwargs): super().__init__(*args, **kwargs) self.assignment = assignment self.helper = FormHelper() - self.helper.form_method = 'post' - self.helper.form_class = 'g-3' - self.helper.enctype = 'multipart/form-data' + self.helper.form_method = "post" + self.helper.form_class = "g-3" + self.helper.enctype = "multipart/form-data" - self.helper.layout = Layout( - Row( - Column('first_name', css_class='col-md-6'), - Column('last_name', css_class='col-md-6'), - css_class='g-3 mb-3' - ), - Row( - Column('email', css_class='col-md-6'), - Column('phone', css_class='col-md-6'), - css_class='g-3 mb-3' - ), - Field('resume', css_class='form-control'), - Div( - Submit('submit', _('Submit Candidate'), css_class='btn btn-main-action'), - css_class='col-12 mt-4' - ) - ) + # self.helper.layout = Layout( + # Row( + # Column("first_name", css_class="col-md-6"), + # Column("last_name", css_class="col-md-6"), + # css_class="g-3 mb-3", + # ), + # Row( + # Column("email", css_class="col-md-6"), + # Column("phone", css_class="col-md-6"), + # css_class="g-3 mb-3", + # ), + # Field("resume", css_class="form-control"), + # Div( + # Submit( + # "submit", _("Submit Candidate"), css_class="btn btn-main-action" + # ), + # css_class="col-12 mt-4", + # ), + # ) def clean_email(self): """Validate email format and check for duplicates in the same job""" - email = self.cleaned_data.get('email') + email = self.cleaned_data.get("email") if email: - # Check if candidate with this email already exists for this job - existing_candidate = Candidate.objects.filter( - email=email.lower().strip(), - job=self.assignment.job + # Check if person with this email already exists for this job + from .models import Person + existing_person = Person.objects.filter( + email=email.lower().strip() ).first() - if existing_candidate: - raise ValidationError( - f'A candidate with this email has already applied for {self.assignment.job.title}.' - ) + if existing_person: + # Check if this person already has an application for this job + existing_application = Application.objects.filter( + person=existing_person, job=self.assignment.job + ).first() + + if existing_application: + raise ValidationError( + f"A candidate with this email has already applied for {self.assignment.job.title}." + ) return email.lower().strip() if email else email def clean_resume(self): """Validate resume file""" - resume = self.cleaned_data.get('resume') + resume = self.cleaned_data.get("resume") if resume: # Check file size (max 5MB) if resume.size > 5 * 1024 * 1024: - raise ValidationError('Resume file size must be less than 5MB.') + raise ValidationError("Resume file size must be less than 5MB.") # Check file extension - allowed_extensions = ['.pdf', '.doc', '.docx'] - file_extension = resume.name.lower().split('.')[-1] - if f'.{file_extension}' not in allowed_extensions: - raise ValidationError( - 'Resume must be in PDF, DOC, or DOCX format.' - ) + allowed_extensions = [".pdf", ".doc", ".docx"] + file_extension = resume.name.lower().split(".")[-1] + if f".{file_extension}" not in allowed_extensions: + raise ValidationError("Resume must be in PDF, DOC, or DOCX format.") return resume def save(self, commit=True): """Override save to set additional fields""" instance = super().save(commit=False) + # Create or get person + from .models import Person + person, created = Person.objects.get_or_create( + email=self.cleaned_data['email'].lower().strip(), + defaults={ + 'first_name': self.cleaned_data['first_name'], + 'last_name': self.cleaned_data['last_name'], + 'phone': self.cleaned_data['phone'], + } + ) + + if not created: + # Update existing person with new info + person.first_name = self.cleaned_data['first_name'] + person.last_name = self.cleaned_data['last_name'] + person.phone = self.cleaned_data['phone'] + person.save() + # Set required fields for agency submission + instance.person = person instance.job = self.assignment.job instance.hiring_agency = self.assignment.agency - instance.stage = Candidate.Stage.APPLIED - instance.applicant_status = Candidate.ApplicantType.CANDIDATE + instance.stage = Application.Stage.APPLIED + instance.applicant_status = Application.ApplicantType.CANDIDATE instance.applied = True if commit: @@ -1149,21 +1345,52 @@ class AgencyLoginForm(forms.Form): """Form for agencies to login with token and password""" token = forms.CharField( - widget=forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter your access token' - }), - label=_('Access Token'), - required=True + widget=forms.TextInput( + attrs={"class": "form-control", "placeholder": "Enter your access token"} + ), + label=_("Access Token"), + required=True, ) password = forms.CharField( - widget=forms.PasswordInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter your password' - }), - label=_('Password'), - required=True + widget=forms.PasswordInput( + attrs={"class": "form-control", "placeholder": "Enter your password"} + ), + label=_("Password"), + required=True, + ) + + +class PortalLoginForm(forms.Form): + """Unified login form for agency and candidate""" + + USER_TYPE_CHOICES = [ + ("", _("Select User Type")), + ("agency", _("Agency")), + ("candidate", _("Candidate")), + ] + + email = forms.EmailField( + widget=forms.EmailInput( + attrs={"class": "form-control", "placeholder": "Enter your email"} + ), + label=_("Email"), + required=True, + ) + + password = forms.CharField( + widget=forms.PasswordInput( + attrs={"class": "form-control", "placeholder": "Enter your password"} + ), + label=_("Password"), + required=True, + ) + + user_type = forms.ChoiceField( + choices=USER_TYPE_CHOICES, + widget=forms.Select(attrs={"class": "form-control"}), + label=_("User Type"), + required=True, ) # def __init__(self, *args, **kwargs): @@ -1184,84 +1411,84 @@ class AgencyLoginForm(forms.Form): def clean(self): """Validate token and password combination""" cleaned_data = super().clean() - token = cleaned_data.get('token') - password = cleaned_data.get('password') + token = cleaned_data.get("token") + password = cleaned_data.get("password") if token and password: try: access_link = AgencyAccessLink.objects.get( - unique_token=token, - is_active=True + unique_token=token, is_active=True ) if not access_link.is_valid: if access_link.is_expired: - raise ValidationError('This access link has expired.') + raise ValidationError("This access link has expired.") else: - raise ValidationError('This access link is no longer active.') + raise ValidationError("This access link is no longer active.") if access_link.access_password != password: - raise ValidationError('Invalid password.') + raise ValidationError("Invalid password.") # Store the access_link for use in the view self.validated_access_link = access_link except AgencyAccessLink.DoesNotExist: print("Access link does not exist") - raise ValidationError('Invalid access token.') + raise ValidationError("Invalid access token.") return cleaned_data - - - -#participants form +# participants form class ParticipantsForm(forms.ModelForm): """Form for creating and editing Participants""" class Meta: - model = Participants - fields = ['name', 'email', 'phone', 'designation'] + model = Participants + fields = ["name", "email", "phone", "designation"] widgets = { - 'name': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter participant name', - 'required': True - }), - 'email': forms.EmailInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter email address', - 'required': True - }), - 'phone': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter phone number' - }), - 'designation': forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter designation' - }), + "name": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Enter participant name", + "required": True, + } + ), + "email": forms.EmailInput( + attrs={ + "class": "form-control", + "placeholder": "Enter email address", + "required": True, + } + ), + "phone": forms.TextInput( + attrs={"class": "form-control", "placeholder": "Enter phone number"} + ), + "designation": forms.TextInput( + attrs={"class": "form-control", "placeholder": "Enter designation"} + ), # 'jobs': forms.CheckboxSelectMultiple(), } -# class ParticipantsSelectForm(forms.ModelForm): -# """Form for selecting Participants""" +class ParticipantsSelectForm(forms.ModelForm): + """Form for selecting Participants""" -# participants=forms.ModelMultipleChoiceField( -# queryset=Participants.objects.all(), -# widget=forms.CheckboxSelectMultiple, -# required=False, -# label=_("Select Participants")) + participants = forms.ModelMultipleChoiceField( + queryset=Participants.objects.all(), + widget=forms.CheckboxSelectMultiple, + required=False, + label=_("Select Participants"), + ) -# users=forms.ModelMultipleChoiceField( -# queryset=User.objects.all(), -# widget=forms.CheckboxSelectMultiple, -# required=False, -# label=_("Select Users")) + users = forms.ModelMultipleChoiceField( + queryset=User.objects.all(), + widget=forms.CheckboxSelectMultiple, + required=False, + label=_("Select Users"), + ) -# class Meta: -# model = JobPosting -# fields = ['participants','users'] # No direct fields from Participants model + class Meta: + model = JobPosting + fields = ["participants", "users"] # No direct fields from Participants model class CandidateEmailForm(forms.Form): @@ -1273,61 +1500,79 @@ class CandidateEmailForm(forms.Form): label=_('Select Candidates'), # Use a descriptive label required=False ) - - subject = forms.CharField( max_length=200, - widget=forms.TextInput(attrs={ - 'class': 'form-control', - 'placeholder': 'Enter email subject', - 'required': True - }), - label=_('Subject'), - required=True + widget=forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Enter email subject", + "required": True, + } + ), + label=_("Subject"), + required=True, ) message = forms.CharField( - widget=forms.Textarea(attrs={ - 'class': 'form-control', - 'rows': 8, - 'placeholder': 'Enter your message here...', - 'required': True - }), - label=_('Message'), - required=True + widget=forms.Textarea( + attrs={ + "class": "form-control", + "rows": 8, + "placeholder": "Enter your message here...", + "required": True, + } + ), + label=_("Message"), + required=True, ) - + recipients = forms.MultipleChoiceField( + widget=forms.CheckboxSelectMultiple(attrs={"class": "form-check"}), + label=_("Recipients"), + required=True, + ) + include_candidate_info = forms.BooleanField( + widget=forms.CheckboxInput(attrs={"class": "form-check-input"}), + label=_("Include candidate information"), + initial=True, + required=False, + ) + + include_meeting_details = forms.BooleanField( + widget=forms.CheckboxInput(attrs={"class": "form-check-input"}), + label=_("Include meeting details"), + initial=True, + required=False, + ) def __init__(self, job, candidates, *args, **kwargs): super().__init__(*args, **kwargs) self.job = job self.candidates=candidates - + candidate_choices=[] for candidate in candidates: candidate_choices.append( (f'candidate_{candidate.id}', f'{candidate.email}') ) - + self.fields['to'].choices =candidate_choices - self.fields['to'].initial = [choice[0] for choice in candidate_choices] - - + self.fields['to'].initial = [choice[0] for choice in candidate_choices] + + # Set initial message with candidate and meeting info initial_message = self._get_initial_message() - if initial_message: - self.fields['message'].initial = initial_message + self.fields["message"].initial = initial_message def _get_initial_message(self): """Generate initial message with candidate and meeting information""" candidate=self.candidates.first() message_parts=[] - + if candidate and candidate.stage == 'Applied': message_parts = [ f"Than you, for your interest in the {self.job.title} role.", @@ -1347,7 +1592,7 @@ class CandidateEmailForm(forms.Form): f"We look forward to reviewing your results.", f"Best regards, The KAAUH Hiring team" ] - + elif candidate and candidate.stage == 'Interview': message_parts = [ f"Than you, for your interest in the {self.job.title} role.", @@ -1358,7 +1603,7 @@ class CandidateEmailForm(forms.Form): f"We look forward to reviewing your results.", f"Best regards, The KAAUH Hiring team" ] - + elif candidate and candidate.stage == 'Offer': message_parts = [ f"Congratulations, ! We are delighted to inform you that we are extending a formal offer of employment for the {self.job.title} role.", @@ -1377,16 +1622,6 @@ class CandidateEmailForm(forms.Form): f"If you have any questions before your start date, please contact [Onboarding Contact].", f"Best regards, The KAAUH Hiring team" ] - - - - - # # Add candidate information - # if self.candidate: - # message_parts.append(f"Candidate Information:") - # message_parts.append(f"Name: {self.candidate.name}") - # message_parts.append(f"Email: {self.candidate.email}") - # message_parts.append(f"Phone: {self.candidate.phone}") # # Add latest meeting information if available # latest_meeting = self.candidate.get_latest_meeting @@ -1398,33 +1633,42 @@ class CandidateEmailForm(forms.Form): # if latest_meeting.join_url: # message_parts.append(f"Join URL: {latest_meeting.join_url}") - return '\n'.join(message_parts) + return "\n".join(message_parts) + def clean_recipients(self): + """Ensure at least one recipient is selected""" + recipients = self.cleaned_data.get('recipients') + if not recipients: + raise forms.ValidationError(_('Please select at least one recipient.')) + return recipients def get_email_addresses(self): """Extract email addresses from selected recipients""" email_addresses = [] - - candidates=self.cleaned_data.get('to',[]) - - if candidates: - for candidate in candidates: - if candidate.startswith('candidate_'): - print("candidadte: {candidate}") - candidate_id = candidate.split('_')[1] - try: - candidate = Candidate.objects.get(id=candidate_id) - email_addresses.append(candidate.email) - except Candidate.DoesNotExist: - continue + recipients = self.cleaned_data.get('recipients', []) + for recipient in recipients: + if recipient.startswith('participant_'): + participant_id = recipient.split('_')[1] + try: + participant = Participants.objects.get(id=participant_id) + email_addresses.append(participant.email) + except Participants.DoesNotExist: + continue + elif recipient.startswith('user_'): + user_id = recipient.split('_')[1] + try: + user = User.objects.get(id=user_id) + email_addresses.append(user.email) + except User.DoesNotExist: + continue return list(set(email_addresses)) # Remove duplicates - + def get_formatted_message(self): - """Get the formatted message with optional additional information""" - message = self.cleaned_data.get('message', '') + """Get formatted message with optional additional information""" + message = self.cleaned_data.get("message", "") return message @@ -1432,9 +1676,9 @@ class CandidateEmailForm(forms.Form): class InterviewParticpantsForm(forms.ModelForm): participants = forms.ModelMultipleChoiceField( queryset=Participants.objects.all(), - widget=forms.CheckboxSelectMultiple, + widget=forms.CheckboxSelectMultiple, required=False , - + ) system_users=forms.ModelMultipleChoiceField( queryset=User.objects.all(), @@ -1490,10 +1734,10 @@ class InterviewEmailForm(forms.Form): label=_('Message'), required=False ) - + def __init__(self, *args,candidate, external_participants, system_participants,meeting,job,**kwargs): super().__init__(*args, **kwargs) - + # --- Data Preparation --- # Note: Added error handling for agency name if it's missing (though it shouldn't be based on your check) formatted_date = meeting.start_time.strftime('%Y-%m-%d') @@ -1502,11 +1746,11 @@ class InterviewEmailForm(forms.Form): duration = meeting.duration job_title = job.title agency_name = candidate.hiring_agency.name if candidate.belong_to_an_agency and candidate.hiring_agency else "Hiring Agency" - + # --- Combined Participants List for Internal Email --- external_participants_names = ", ".join([p.name for p in external_participants ]) system_participants_names = ", ".join([p.first_name for p in system_participants ]) - + # Combine and ensure no leading/trailing commas if one list is empty participant_names = ", ".join(filter(None, [external_participants_names, system_participants_names])) @@ -1533,7 +1777,7 @@ We look forward to meeting you. Best regards, KAAUH Hiring Team """ - + # --- 2. Agency Message (Professional and clear details) --- agency_message = f""" @@ -1583,14 +1827,14 @@ Thank you for your participation. Best regards, KAAUH HIRING TEAM """ - + # Set initial data self.initial['subject'] = f"Interview Invitation: {job_title} at KAAUH - {candidate.full_name}" # .strip() removes the leading/trailing blank lines caused by the f""" format self.initial['message_for_candidate'] = candidate_message.strip() self.initial['message_for_agency'] = agency_message.strip() self.initial['message_for_participants'] = participants_message.strip() - + @@ -1602,7 +1846,7 @@ KAAUH HIRING TEAM # 'location': forms.TextInput(attrs={'placeholder': 'Enter Interview Location'}), # } - + class OnsiteMeetingForm(forms.ModelForm): class Meta: @@ -1617,6 +1861,171 @@ class OnsiteMeetingForm(forms.ModelForm): attrs={'min': 15, 'placeholder': 'Duration in minutes', 'class': 'form-control'} ), 'location': forms.TextInput(attrs={'placeholder': 'Physical location', 'class': 'form-control'}), - 'timezone': forms.TextInput(attrs={'class': 'form-control'}), + 'timezone': forms.TextInput(attrs={'class': 'form-control'}), 'status': forms.Select(attrs={'class': 'form-control'}), - } \ No newline at end of file + } + + +class MessageForm(forms.ModelForm): + """Form for creating and editing messages between users""" + + class Meta: + model = Message + fields = ["recipient", "job", "subject", "content", "message_type"] + widgets = { + "recipient": forms.Select( + attrs={"class": "form-select", "placeholder": "Select recipient"} + ), + "job": forms.Select( + attrs={"class": "form-select", "placeholder": "Select job (optional)"} + ), + "subject": forms.TextInput( + attrs={ + "class": "form-control", + "placeholder": "Enter message subject", + "required": True, + } + ), + "content": forms.Textarea( + attrs={ + "class": "form-control", + "rows": 6, + "placeholder": "Enter your message here...", + "required": True, + } + ), + "message_type": forms.Select(attrs={"class": "form-select"}), + } + labels = { + "recipient": _("Recipient"), + "job": _("Related Job"), + "subject": _("Subject"), + "content": _("Message"), + "message_type": _("Message Type"), + } + + def __init__(self, user, *args, **kwargs): + super().__init__(*args, **kwargs) + self.user = user + self.helper = FormHelper() + self.helper.form_method = "post" + self.helper.form_class = "g-3" + + # Filter job options based on user type + self._filter_job_field() + + # Filter recipient options based on user type + self._filter_recipient_field() + + self.helper.layout = Layout( + Row( + Column("recipient", css_class="col-md-6"), + Column("job", css_class="col-md-6"), + css_class="g-3 mb-3", + ), + Field("subject", css_class="form-control"), + Field("message_type", css_class="form-control"), + Field("content", css_class="form-control"), + Div( + Submit("submit", _("Send Message"), css_class="btn btn-main-action"), + css_class="col-12 mt-4", + ), + ) + + def _filter_job_field(self): + """Filter job options based on user type""" + if self.user.user_type == "agency": + # Agency users can only see jobs assigned to their agency + self.fields["job"].queryset = JobPosting.objects.filter( + hiring_agency__user=self.user, + status="ACTIVE" + ).order_by("-created_at") + elif self.user.user_type == "candidate": + # Candidates can only see jobs they applied for + self.fields["job"].queryset = JobPosting.objects.filter( + candidates__user=self.user + ).distinct().order_by("-created_at") + else: + # Staff can see all jobs + self.fields["job"].queryset = JobPosting.objects.filter( + status="ACTIVE" + ).order_by("-created_at") + + def _filter_recipient_field(self): + """Filter recipient options based on user type""" + if self.user.user_type == "staff": + # Staff can message anyone + self.fields["recipient"].queryset = User.objects.all().order_by("username") + elif self.user.user_type == "agency": + # Agency can message staff and their candidates + from django.db.models import Q + self.fields["recipient"].queryset = User.objects.filter( + Q(user_type="staff") | + Q(candidate_profile__job__hiring_agency__user=self.user) + ).distinct().order_by("username") + elif self.user.user_type == "candidate": + # Candidates can only message staff + self.fields["recipient"].queryset = User.objects.filter( + user_type="staff" + ).order_by("username") + + def clean(self): + """Validate message form data""" + cleaned_data = super().clean() + + job = cleaned_data.get("job") + recipient = cleaned_data.get("recipient") + + # If job is selected but no recipient, auto-assign to job.assigned_to + if job and not recipient: + if job.assigned_to: + cleaned_data["recipient"] = job.assigned_to + # Set message type to job_related + cleaned_data["message_type"] = Message.MessageType.JOB_RELATED + else: + raise forms.ValidationError( + _("Selected job is not assigned to any user. Please assign the job first.") + ) + + # Validate messaging permissions + if self.user and cleaned_data.get("recipient"): + self._validate_messaging_permissions(cleaned_data) + + return cleaned_data + + def _validate_messaging_permissions(self, cleaned_data): + """Validate if user can message the recipient""" + recipient = cleaned_data.get("recipient") + job = cleaned_data.get("job") + + # Staff can message anyone + if self.user.user_type == "staff": + return + + # Agency users validation + if self.user.user_type == "agency": + if recipient.user_type not in ["staff", "candidate"]: + raise forms.ValidationError( + _("Agencies can only message staff or candidates.") + ) + + # If messaging a candidate, ensure candidate is from their agency + if recipient.user_type == "candidate" and job: + if not job.hiring_agency.filter(user=self.user).exists(): + raise forms.ValidationError( + _("You can only message candidates from your assigned jobs.") + ) + + # Candidate users validation + if self.user.user_type == "candidate": + if recipient.user_type != "staff": + raise forms.ValidationError( + _("Candidates can only message staff.") + ) + + # If job-related, ensure candidate applied for the job + if job: + if not Candidate.objects.filter(job=job, user=self.user).exists(): + raise forms.ValidationError( + _("You can only message about jobs you have applied for.") + ) diff --git a/recruitment/management/__pycache__/__init__.cpython-313.pyc b/recruitment/management/__pycache__/__init__.cpython-313.pyc index 2374032..8a43ab6 100644 Binary files a/recruitment/management/__pycache__/__init__.cpython-313.pyc and b/recruitment/management/__pycache__/__init__.cpython-313.pyc differ diff --git a/recruitment/management/commands/translate_po.py b/recruitment/management/commands/translate_po.py new file mode 100644 index 0000000..cb5692f --- /dev/null +++ b/recruitment/management/commands/translate_po.py @@ -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}") \ No newline at end of file diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py index 6e83eef..2e786ed 100644 --- a/recruitment/migrations/0001_initial.py +++ b/recruitment/migrations/0001_initial.py @@ -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-13 13:12 +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,8 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('auth', '0012_alter_user_first_name_max_length'), + ('contenttypes', '0002_remove_content_type_name'), ] operations = [ @@ -45,25 +49,21 @@ class Migration(migrations.Migration): }, ), migrations.CreateModel( - name='HiringAgency', + name='OnsiteMeeting', 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)), + ('topic', models.CharField(max_length=255, verbose_name='Topic')), + ('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')), + ('duration', models.PositiveIntegerField(verbose_name='Duration')), + ('timezone', models.CharField(max_length=50, verbose_name='Timezone')), + ('location', models.CharField(blank=True, null=True)), + ('status', models.CharField(blank=True, db_index=True, default='waiting', max_length=20, null=True, verbose_name='Status')), ], options={ - 'verbose_name': 'Hiring Agency', - 'verbose_name_plural': 'Hiring Agencies', - 'ordering': ['name'], + 'abstract': False, }, ), migrations.CreateModel( @@ -137,6 +137,33 @@ 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='FormField', fields=[ @@ -206,40 +233,58 @@ 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.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')), + ('hiring_agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='recruitment.hiringagency', verbose_name='Hiring Agency')), + ], + options={ + 'verbose_name': 'Application', + 'verbose_name_plural': 'Applications', }, ), migrations.CreateModel( @@ -251,8 +296,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,9 +326,8 @@ 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')), ('source', models.ForeignKey(blank=True, help_text='The system or channel from which this job posting originated or was first published.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='job_postings', to='recruitment.source')), ], options={ @@ -299,6 +343,7 @@ class Migration(migrations.Migration): ('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')), + ('interview_type', models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type')), ('start_date', models.DateField(db_index=True, verbose_name='Start Date')), ('end_date', models.DateField(db_index=True, verbose_name='End Date')), ('working_days', models.JSONField(verbose_name='Working Days')), @@ -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')), + ('applications', 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,59 @@ 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')), + ('agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='recruitment.hiringagency', verbose_name='Hiring Agency')), + ('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.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,10 +533,13 @@ 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')), + ('onsite_meeting', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='onsite_interview', to='recruitment.onsitemeeting')), + ('participants', models.ManyToManyField(blank=True, to='recruitment.participants')), ('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')), + ('system_users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('zoom_meeting', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting')), ], ), migrations.CreateModel( @@ -501,6 +602,27 @@ class Migration(migrations.Migration): 'indexes': [models.Index(fields=['unique_token'], name='recruitment_unique__f91e76_idx'), models.Index(fields=['expires_at'], name='recruitment_expires_954ed9_idx'), models.Index(fields=['is_active'], name='recruitment_is_acti_4b0804_idx')], }, ), + 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')), + ('object_id', models.PositiveIntegerField(verbose_name='Object ID')), + ('file', models.FileField(upload_to='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')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='Content Type')), + ('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')), + ], + options={ + 'verbose_name': 'Document', + 'verbose_name_plural': 'Documents', + 'ordering': ['-created_at'], + 'indexes': [models.Index(fields=['content_type', 'object_id', 'document_type', 'created_at'], name='recruitment_content_547650_idx')], + }, + ), migrations.CreateModel( name='FieldResponse', fields=[ @@ -543,14 +665,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 +685,54 @@ 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='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 +751,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', diff --git a/recruitment/migrations/0002_scheduledinterview_participants.py b/recruitment/migrations/0002_scheduledinterview_participants.py deleted file mode 100644 index 71c0a2c..0000000 --- a/recruitment/migrations/0002_scheduledinterview_participants.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-06 15:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='scheduledinterview', - name='participants', - field=models.ManyToManyField(blank=True, to='recruitment.participants'), - ), - ] diff --git a/recruitment/migrations/0003_scheduledinterview_system_users.py b/recruitment/migrations/0003_scheduledinterview_system_users.py deleted file mode 100644 index e365758..0000000 --- a/recruitment/migrations/0003_scheduledinterview_system_users.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-06 15:37 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0002_scheduledinterview_participants'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='scheduledinterview', - name='system_users', - field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/recruitment/migrations/0004_remove_jobposting_participants_and_more.py b/recruitment/migrations/0004_remove_jobposting_participants_and_more.py deleted file mode 100644 index 9368b3a..0000000 --- a/recruitment/migrations/0004_remove_jobposting_participants_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-06 15:44 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0003_scheduledinterview_system_users'), - ] - - operations = [ - migrations.RemoveField( - model_name='jobposting', - name='participants', - ), - migrations.RemoveField( - model_name='jobposting', - name='users', - ), - ] diff --git a/recruitment/migrations/0005_scheduledinterview_meeting_type.py b/recruitment/migrations/0005_scheduledinterview_meeting_type.py deleted file mode 100644 index fe94194..0000000 --- a/recruitment/migrations/0005_scheduledinterview_meeting_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 11:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0004_remove_jobposting_participants_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='scheduledinterview', - name='meeting_type', - field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'), - ), - ] diff --git a/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py b/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py deleted file mode 100644 index 9270e59..0000000 --- a/recruitment/migrations/0006_remove_scheduledinterview_meeting_type_and_more.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 11:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0005_scheduledinterview_meeting_type'), - ] - - operations = [ - migrations.RemoveField( - model_name='scheduledinterview', - name='meeting_type', - ), - migrations.AddField( - model_name='interviewschedule', - name='meeting_type', - field=models.CharField(choices=[('Remote', 'Remote Interview'), ('Onsite', 'In-Person Interview')], default='Remote', max_length=10, verbose_name='Interview Meeting Type'), - ), - ] diff --git a/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py b/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py deleted file mode 100644 index 85e6f83..0000000 --- a/recruitment/migrations/0007_rename_meeting_type_interviewschedule_interview_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 11:30 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0006_remove_scheduledinterview_meeting_type_and_more'), - ] - - operations = [ - migrations.RenameField( - model_name='interviewschedule', - old_name='meeting_type', - new_name='interview_type', - ), - ] diff --git a/recruitment/migrations/0008_interviewschedule_location.py b/recruitment/migrations/0008_interviewschedule_location.py deleted file mode 100644 index 2800b75..0000000 --- a/recruitment/migrations/0008_interviewschedule_location.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 12:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0007_rename_meeting_type_interviewschedule_interview_type'), - ] - - operations = [ - migrations.AddField( - model_name='interviewschedule', - name='location', - field=models.CharField(blank=True, default='Remote', null=True), - ), - ] diff --git a/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py deleted file mode 100644 index 602917a..0000000 --- a/recruitment/migrations/0009_alter_zoommeeting_meeting_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 13:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0008_interviewschedule_location'), - ] - - operations = [ - migrations.AlterField( - model_name='zoommeeting', - name='meeting_id', - field=models.CharField(blank=True, db_index=True, max_length=20, null=True, unique=True, verbose_name='Meeting ID'), - ), - ] diff --git a/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py b/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py deleted file mode 100644 index d64f578..0000000 --- a/recruitment/migrations/0010_alter_zoommeeting_meeting_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 13:48 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0009_alter_zoommeeting_meeting_id'), - ] - - operations = [ - migrations.AlterField( - model_name='zoommeeting', - name='meeting_id', - field=models.CharField(db_index=True, default=1, max_length=20, unique=True, verbose_name='Meeting ID'), - preserve_default=False, - ), - ] diff --git a/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py b/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py deleted file mode 100644 index dba9c62..0000000 --- a/recruitment/migrations/0011_alter_scheduledinterview_zoom_meeting.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-09 13:50 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0010_alter_zoommeeting_meeting_id'), - ] - - operations = [ - migrations.AlterField( - model_name='scheduledinterview', - name='zoom_meeting', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting'), - ), - ] diff --git a/recruitment/migrations/0012_interviewschedule_interview_topic.py b/recruitment/migrations/0012_interviewschedule_interview_topic.py deleted file mode 100644 index fbf21b7..0000000 --- a/recruitment/migrations/0012_interviewschedule_interview_topic.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-10 09:27 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0011_alter_scheduledinterview_zoom_meeting'), - ] - - operations = [ - migrations.AddField( - model_name='interviewschedule', - name='interview_topic', - field=models.CharField(blank=True, null=True), - ), - ] diff --git a/recruitment/migrations/0013_onsitemeeting_and_more.py b/recruitment/migrations/0013_onsitemeeting_and_more.py deleted file mode 100644 index 4cb09d6..0000000 --- a/recruitment/migrations/0013_onsitemeeting_and_more.py +++ /dev/null @@ -1,45 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-10 13:00 - -import django.db.models.deletion -import django_extensions.db.fields -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0012_interviewschedule_interview_topic'), - ] - - operations = [ - migrations.CreateModel( - name='OnsiteMeeting', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), - ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), - ('topic', models.CharField(max_length=255, verbose_name='Topic')), - ('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')), - ('duration', models.PositiveIntegerField(verbose_name='Duration')), - ('timezone', models.CharField(max_length=50, verbose_name='Timezone')), - ('location', models.CharField(blank=True, null=True)), - ], - options={ - 'abstract': False, - }, - ), - migrations.RemoveField( - model_name='interviewschedule', - name='interview_topic', - ), - migrations.RemoveField( - model_name='interviewschedule', - name='location', - ), - migrations.AddField( - model_name='scheduledinterview', - name='onsite_meeting', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.onsitemeeting'), - ), - ] diff --git a/recruitment/migrations/0014_onsitemeeting_status.py b/recruitment/migrations/0014_onsitemeeting_status.py deleted file mode 100644 index 78270f1..0000000 --- a/recruitment/migrations/0014_onsitemeeting_status.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-10 13:20 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0013_onsitemeeting_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='onsitemeeting', - name='status', - field=models.CharField(blank=True, db_index=True, default='waiting', max_length=20, null=True, verbose_name='Status'), - ), - ] diff --git a/recruitment/migrations/0015_alter_scheduledinterview_onsite_meeting.py b/recruitment/migrations/0015_alter_scheduledinterview_onsite_meeting.py deleted file mode 100644 index 3127c6b..0000000 --- a/recruitment/migrations/0015_alter_scheduledinterview_onsite_meeting.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.2.7 on 2025-11-10 13:55 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('recruitment', '0014_onsitemeeting_status'), - ] - - operations = [ - migrations.AlterField( - model_name='scheduledinterview', - name='onsite_meeting', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='onsite_interview', to='recruitment.onsitemeeting'), - ), - ] diff --git a/recruitment/migrations/__pycache__/0001_initial.cpython-313.pyc b/recruitment/migrations/__pycache__/0001_initial.cpython-313.pyc index b86dad7..e3b2eb3 100644 Binary files a/recruitment/migrations/__pycache__/0001_initial.cpython-313.pyc and b/recruitment/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/recruitment/models.py b/recruitment/models.py index bd25ec7..fab3793 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -1,11 +1,12 @@ from django.db import models from django.urls import reverse -from typing import List,Dict,Any +from typing import List, Dict, Any from django.utils import timezone -from django.db.models import FloatField,CharField,IntegerField -from django.db.models.functions import Cast,Coalesce +from django.db.models import FloatField, CharField, IntegerField +from django.db.models.functions import Cast, Coalesce from django.db.models import F -from django.contrib.auth.models import User +from django.contrib.auth.models import AbstractUser +from django.contrib.auth import get_user_model from django.core.validators import URLValidator from django_countries.fields import CountryField from django.core.exceptions import ValidationError @@ -14,6 +15,33 @@ from django.utils.html import strip_tags from django.utils.translation import gettext_lazy as _ from django_extensions.db.fields import RandomCharField from .validators import validate_hash_tags, validate_image_size +from django.contrib.auth.models import AbstractUser +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType + + +class CustomUser(AbstractUser): + """Custom user model extending AbstractUser""" + + USER_TYPES = [ + ("staff", _("Staff")), + ("agency", _("Agency")), + ("candidate", _("Candidate")), + ] + + user_type = models.CharField( + max_length=20, choices=USER_TYPES, default="staff", verbose_name=_("User Type") + ) + phone = models.CharField( + max_length=20, blank=True, null=True, verbose_name=_("Phone") + ) + + class Meta: + verbose_name = _("User") + verbose_name_plural = _("Users") + + +User = get_user_model() class Base(models.Model): @@ -26,10 +54,18 @@ class Base(models.Model): class Meta: abstract = True + class Profile(models.Model): - profile_image = models.ImageField(null=True, blank=True, upload_to="profile_pic/",validators=[validate_image_size]) - designation = models.CharField(max_length=100, blank=True,null=True) - phone=models.CharField(blank=True,null=True,verbose_name=_("Phone Number"),max_length=12) + profile_image = models.ImageField( + null=True, + blank=True, + upload_to="profile_pic/", + validators=[validate_image_size], + ) + designation = models.CharField(max_length=100, blank=True, null=True) + phone = models.CharField( + blank=True, null=True, verbose_name=_("Phone Number"), max_length=12 + ) user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile") def __str__(self): @@ -40,18 +76,18 @@ class JobPosting(Base): # Basic Job Information JOB_TYPES = [ - ("FULL_TIME", "Full-time"), - ("PART_TIME", "Part-time"), - ("CONTRACT", "Contract"), - ("INTERNSHIP", "Internship"), - ("FACULTY", "Faculty"), - ("TEMPORARY", "Temporary"), + (_("Full-time"), _("Full-time")), + (_("Part-time"), _("Part-time")), + (_("Contract"), _("Contract")), + (_("Internship"), _("Internship")), + (_("Faculty"), _("Faculty")), + (_("Temporary"), _("Temporary")), ] WORKPLACE_TYPES = [ - ("ON_SITE", "On-site"), - ("REMOTE", "Remote"), - ("HYBRID", "Hybrid"), + (_("On-site"), _("On-site")), + (_("Remote"), _("Remote")), + (_("Hybrid"), _("Hybrid")), ] # users=models.ManyToManyField( @@ -82,17 +118,15 @@ class JobPosting(Base): # Job Details description = CKEditor5Field( - 'Description', - config_name='extends' # Matches the config name you defined in settings.py + "Description", + config_name="extends", # Matches the config name you defined in settings.py ) - qualifications = CKEditor5Field(blank=True, null=True, - config_name='extends' - ) + qualifications = CKEditor5Field(blank=True, null=True, config_name="extends") salary_range = models.CharField( max_length=200, blank=True, help_text="e.g., $60,000 - $80,000" ) - benefits = CKEditor5Field(blank=True, null=True, config_name='extends') + benefits = CKEditor5Field(blank=True, null=True, config_name="extends") # Application Information ---job detail apply link for the candidates application_url = models.URLField( @@ -104,7 +138,7 @@ class JobPosting(Base): application_deadline = models.DateField(db_index=True) # Added index application_instructions = CKEditor5Field( - blank=True, null=True, config_name='extends' + blank=True, null=True, config_name="extends" ) # Internal Tracking @@ -123,7 +157,10 @@ class JobPosting(Base): ("ARCHIVED", "Archived"), ] status = models.CharField( - db_index=True, max_length=20, choices=STATUS_CHOICES, default="DRAFT" # Added index + db_index=True, + max_length=20, + choices=STATUS_CHOICES, + default="DRAFT", # Added index ) # hashtags for social media @@ -146,9 +183,11 @@ class JobPosting(Base): max_length=50, blank=True, help_text="Status of LinkedIn posting" ) linkedin_posted_at = models.DateTimeField(null=True, blank=True) - linkedin_post_formated_data=models.TextField(null=True,blank=True) + linkedin_post_formated_data = models.TextField(null=True, blank=True) - published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index + published_at = models.DateTimeField( + db_index=True, null=True, blank=True + ) # Added index # University Specific Fields position_number = models.CharField( max_length=50, blank=True, help_text="University position number" @@ -168,10 +207,13 @@ class JobPosting(Base): null=True, blank=True, help_text="The system or channel from which this job posting originated or was first published.", - db_index=True # Explicitly index ForeignKey + db_index=True, # Explicitly index ForeignKey ) max_applications = models.PositiveIntegerField( - default=1000, help_text="Maximum number of applications allowed", null=True, blank=True + default=1000, + help_text="Maximum number of applications allowed", + null=True, + blank=True, ) hiring_agency = models.ManyToManyField( "HiringAgency", @@ -194,14 +236,23 @@ class JobPosting(Base): verbose_name=_("Cancelled By"), ) cancelled_at = models.DateTimeField(null=True, blank=True) + assigned_to = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + blank=True, + related_name="assigned_jobs", + help_text=_("The user who has been assigned to this job"), + verbose_name=_("Assigned To"), + ) class Meta: ordering = ["-created_at"] verbose_name = "Job Posting" verbose_name_plural = "Job Postings" indexes = [ - models.Index(fields=['status', 'created_at', 'title']), - models.Index(fields=['slug']), + models.Index(fields=["status", "created_at", "title"]), + models.Index(fields=["slug"]), ] def __str__(self): @@ -220,9 +271,8 @@ class JobPosting(Base): year = timezone.now().year # Get next sequential number last_job = ( - JobPosting.objects.select_for_update().filter( - internal_job_id__startswith=f"{prefix}-{year}-" - ) + JobPosting.objects.select_for_update() + .filter(internal_job_id__startswith=f"{prefix}-{year}-") .order_by("internal_job_id") .last() ) @@ -269,7 +319,7 @@ class JobPosting(Base): return False # 1. Replace the common HTML non-breaking space entity with a standard space. - content = field_value.replace(' ', ' ') + content = field_value.replace(" ", " ") # 2. Remove all HTML tags (leaving only text and remaining spaces). stripped = strip_tags(content) @@ -302,7 +352,7 @@ class JobPosting(Base): @property def current_applications_count(self): """Returns the current number of candidates associated with this job.""" - return self.candidates.count() + return self.applications.count() @property def is_application_limit_reached(self): @@ -314,9 +364,15 @@ class JobPosting(Base): @property def all_candidates(self): - return self.candidates.annotate( - sortable_score=Coalesce(Cast('ai_analysis_data__analysis_data__match_score', output_field=IntegerField()), - 0)).order_by('-sortable_score') + return self.applications.annotate( + sortable_score=Coalesce( + Cast( + "ai_analysis_data__analysis_data__match_score", + output_field=IntegerField(), + ), + 0, + ) + ).order_by("-sortable_score") @property def screening_candidates(self): @@ -333,9 +389,11 @@ class JobPosting(Base): @property def offer_candidates(self): return self.all_candidates.filter(stage="Offer") + @property def accepted_candidates(self): return self.all_candidates.filter(offer_status="Accepted") + @property def hired_candidates(self): return self.all_candidates.filter(stage="Hired") @@ -343,9 +401,16 @@ class JobPosting(Base): # counts @property def all_candidates_count(self): - return self.candidates.annotate( - sortable_score=Cast('ai_analysis_data__match_score', output_field=CharField())).order_by( - '-sortable_score').count() or 0 + return ( + self.candidates.annotate( + sortable_score=Cast( + "ai_analysis_data__match_score", output_field=CharField() + ) + ) + .order_by("-sortable_score") + .count() + or 0 + ) @property def screening_candidates_count(self): @@ -371,7 +436,7 @@ class JobPosting(Base): def vacancy_fill_rate(self): total_positions = self.open_positions - no_of_positions_filled = self.candidates.filter(stage__in=['HIRED']).count() + no_of_positions_filled = self.applications.filter(stage__in=["HIRED"]).count() if total_positions > 0: vacancy_fill_rate = no_of_positions_filled / total_positions @@ -381,27 +446,128 @@ class JobPosting(Base): return vacancy_fill_rate - class JobPostingImage(models.Model): - job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images') - post_image = models.ImageField(upload_to='post/',validators=[validate_image_size]) + job = models.OneToOneField( + "JobPosting", on_delete=models.CASCADE, related_name="post_images" + ) + post_image = models.ImageField(upload_to="post/", validators=[validate_image_size]) -class Candidate(Base): +class Person(Base): + """Model to store personal information that can be reused across multiple applications""" + + GENDER_CHOICES = [ + ("M", _("Male")), + ("F", _("Female")), + ("O", _("Other")), + ("P", _("Prefer not to say")), + ] + + # Personal Information + 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(max_length=255, blank=True, null=True, verbose_name=_("Middle Name")) + email = models.EmailField( + unique=True, + db_index=True, + verbose_name=_("Email"), + help_text=_("Unique email address for the person") + ) + phone = models.CharField(max_length=20, blank=True, null=True, verbose_name=_("Phone")) + date_of_birth = models.DateField(null=True, blank=True, verbose_name=_("Date of Birth")) + gender = models.CharField( + max_length=1, + choices=GENDER_CHOICES, + blank=True, + null=True, + verbose_name=_("Gender") + ) + nationality = CountryField(blank=True, null=True, verbose_name=_("Nationality")) + address = models.TextField(blank=True, null=True, verbose_name=_("Address")) + + # Optional linking to user account + user = models.OneToOneField( + User, + on_delete=models.SET_NULL, + related_name="person_profile", + verbose_name=_("User Account"), + null=True, + blank=True, + ) + + # Profile information + profile_image = models.ImageField( + null=True, + blank=True, + upload_to="profile_pic/", + validators=[validate_image_size], + verbose_name=_("Profile Image") + ) + linkedin_profile = models.URLField( + blank=True, + null=True, + verbose_name=_("LinkedIn Profile URL") + ) + agency = models.ForeignKey( + "HiringAgency", + on_delete=models.SET_NULL, + null=True, + blank=True, + verbose_name=_("Hiring Agency") + ) + class Meta: + verbose_name = _("Person") + verbose_name_plural = _("People") + indexes = [ + models.Index(fields=["email"]), + models.Index(fields=["first_name", "last_name"]), + models.Index(fields=["created_at"]), + ] + + def __str__(self): + return f"{self.first_name} {self.last_name}" + + @property + def full_name(self): + return f"{self.first_name} {self.last_name}" + + @property + def age(self): + """Calculate age from date of birth""" + if self.date_of_birth: + today = timezone.now().date() + return today.year - self.date_of_birth.year - ( + (today.month, today.day) < (self.date_of_birth.month, self.date_of_birth.day) + ) + return None + + @property + def documents(self): + """Return all documents associated with this Person""" + from django.contrib.contenttypes.models import ContentType + content_type = ContentType.objects.get_for_model(self.__class__) + return Document.objects.filter(content_type=content_type, object_id=self.id) + + +class Application(Base): + """Model to store job-specific application data""" + class Stage(models.TextChoices): APPLIED = "Applied", _("Applied") EXAM = "Exam", _("Exam") INTERVIEW = "Interview", _("Interview") OFFER = "Offer", _("Offer") HIRED = "Hired", _("Hired") + REJECTED = "Rejected", _("Rejected") class ExamStatus(models.TextChoices): PASSED = "Passed", _("Passed") FAILED = "Failed", _("Failed") - class Status(models.TextChoices): + class OfferStatus(models.TextChoices): ACCEPTED = "Accepted", _("Accepted") REJECTED = "Rejected", _("Rejected") + PENDING = "Pending", _("Pending") class ApplicantType(models.TextChoices): APPLICANT = "Applicant", _("Applicant") @@ -409,35 +575,50 @@ class Candidate(Base): # Stage transition validation constants STAGE_SEQUENCE = { - "Applied": ["Exam", "Interview", "Offer"], - "Exam": ["Interview", "Offer"], - "Interview": ["Offer"], - "Offer": [], # Final stage - no further transitions + "Applied": ["Exam", "Interview", "Offer", "Rejected"], + "Exam": ["Interview", "Offer", "Rejected"], + "Interview": ["Offer", "Rejected"], + "Offer": ["Hired", "Rejected"], + "Rejected": [], # Final stage - no further transitions + "Hired": [], # Final stage - no further transitions } + # Core relationships + person = models.ForeignKey( + Person, + on_delete=models.CASCADE, + related_name="applications", + verbose_name=_("Person"), + ) job = models.ForeignKey( JobPosting, on_delete=models.CASCADE, - related_name="candidates", + related_name="applications", verbose_name=_("Job"), ) - 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, verbose_name=_("Email")) # Added index - phone = models.CharField(max_length=20, verbose_name=_("Phone")) - address = models.TextField(max_length=200, verbose_name=_("Address")) + # Application-specific data resume = models.FileField(upload_to="resumes/", verbose_name=_("Resume")) + cover_letter = models.FileField( + upload_to="cover_letters/", + blank=True, + null=True, + verbose_name=_("Cover Letter") + ) is_resume_parsed = models.BooleanField( - default=False, verbose_name=_("Resume Parsed") + 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") ) - parsed_summary = models.TextField(blank=True, verbose_name=_("Parsed Summary")) + + # Workflow fields applied = models.BooleanField(default=False, verbose_name=_("Applied")) stage = models.CharField( - db_index=True, max_length=100, # Added index + db_index=True, + max_length=20, default="Applied", choices=Stage.choices, verbose_name=_("Stage"), @@ -445,15 +626,17 @@ class Candidate(Base): applicant_status = models.CharField( choices=ApplicantType.choices, default="Applicant", - max_length=100, + max_length=20, null=True, blank=True, verbose_name=_("Applicant Status"), ) + + # Timeline fields exam_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Exam Date")) exam_status = models.CharField( choices=ExamStatus.choices, - max_length=100, + max_length=20, null=True, blank=True, verbose_name=_("Exam Status"), @@ -463,34 +646,46 @@ class Candidate(Base): ) interview_status = models.CharField( choices=ExamStatus.choices, - max_length=100, + max_length=20, null=True, blank=True, verbose_name=_("Interview Status"), ) offer_date = models.DateField(null=True, blank=True, verbose_name=_("Offer Date")) offer_status = models.CharField( - choices=Status.choices, - max_length=100, + choices=OfferStatus.choices, + max_length=20, null=True, blank=True, verbose_name=_("Offer Status"), ) hired_date = models.DateField(null=True, blank=True, verbose_name=_("Hired Date")) join_date = models.DateField(null=True, blank=True, verbose_name=_("Join Date")) + + # AI Analysis ai_analysis_data = models.JSONField( verbose_name="AI Analysis Data", default=dict, - help_text="Full JSON output from the resume scoring model." - )# {'resume_data': {}, 'analysis_data': {}} + help_text="Full JSON output from the resume scoring model.", + null=True, + blank=True, + ) + retry = models.SmallIntegerField( + verbose_name="Resume Parsing Retry", + default=3 + ) - retry = models.SmallIntegerField(verbose_name="Resume Parsing Retry",default=3) + # Source tracking hiring_source = models.CharField( max_length=255, null=True, blank=True, verbose_name=_("Hiring Source"), - choices=[("Public", "Public"), ("Internal", "Internal"), ("Agency", "Agency")], + choices=[ + (_("Public"), _("Public")), + (_("Internal"), _("Internal")), + (_("Agency"), _("Agency")), + ], default="Public", ) hiring_agency = models.ForeignKey( @@ -498,224 +693,264 @@ class Candidate(Base): on_delete=models.SET_NULL, null=True, blank=True, - related_name="candidates", + related_name="applications", verbose_name=_("Hiring Agency"), ) + # Optional linking to user account (for candidate portal access) + # user = models.OneToOneField( + # User, + # on_delete=models.SET_NULL, + # related_name="application_profile", + # verbose_name=_("User Account"), + # null=True, + # blank=True, + # ) + class Meta: - verbose_name = _("Candidate") - verbose_name_plural = _("Candidates") + verbose_name = _("Application") + verbose_name_plural = _("Applications") indexes = [ - models.Index(fields=['stage']), - models.Index(fields=['created_at']), + models.Index(fields=["person", "job"]), + models.Index(fields=["stage"]), + models.Index(fields=["created_at"]), + models.Index(fields=["person", "stage", "created_at"]), ] - def set_field(self, key: str, value: Any): - """ - Generic method to set any single key-value pair and save. - """ - self.ai_analysis_data[key] = value - # self.save(update_fields=['ai_analysis_data']) + unique_together = [["person", "job"]] # Prevent duplicate applications + + def __str__(self): + return f"{self.person.full_name} - {self.job.title}" # ==================================================================== - # ✨ PROPERTIES (GETTERS) + # ✨ PROPERTIES (GETTERS) - Migrated from Candidate # ==================================================================== @property def resume_data(self): - return self.ai_analysis_data.get('resume_data', {}) + return self.ai_analysis_data.get("resume_data", {}) + @property def analysis_data(self): - return self.ai_analysis_data.get('analysis_data', {}) + return self.ai_analysis_data.get("analysis_data", {}) + @property def match_score(self) -> int: """1. A score from 0 to 100 representing how well the candidate fits the role.""" - return self.analysis_data.get('match_score', 0) + return self.analysis_data.get("match_score", 0) @property def years_of_experience(self) -> float: """4. The total number of years of professional experience as a numerical value.""" - return self.analysis_data.get('years_of_experience', 0.0) + return self.analysis_data.get("years_of_experience", 0.0) @property def soft_skills_score(self) -> int: """15. A score (0-100) for inferred non-technical skills.""" - return self.analysis_data.get('soft_skills_score', 0) + return self.analysis_data.get("soft_skills_score", 0) @property def industry_match_score(self) -> int: """16. A score (0-100) for the relevance of the candidate's industry experience.""" - # Renamed to clarify: experience_industry_match - return self.analysis_data.get('experience_industry_match', 0) - - # --- Properties for Funnel & Screening Efficiency --- + return self.analysis_data.get("experience_industry_match", 0) @property def min_requirements_met(self) -> bool: """14. Boolean (true/false) indicating if all non-negotiable minimum requirements are met.""" - return self.analysis_data.get('min_req_met_bool', False) + return self.analysis_data.get("min_req_met_bool", False) @property def screening_stage_rating(self) -> str: """13. A standardized rating (e.g., "A - Highly Qualified", "B - Qualified").""" - return self.analysis_data.get('screening_stage_rating', 'N/A') + return self.analysis_data.get("screening_stage_rating", "N/A") @property def top_3_keywords(self) -> List[str]: """10. A list of the three most dominant and relevant technical skills or technologies.""" - return self.analysis_data.get('top_3_keywords', []) + return self.analysis_data.get("top_3_keywords", []) @property def most_recent_job_title(self) -> str: """8. The candidate's most recent or current professional job title.""" - return self.analysis_data.get('most_recent_job_title', 'N/A') - - # --- Properties for Structured Detail --- + return self.analysis_data.get("most_recent_job_title", "N/A") @property def criteria_checklist(self) -> Dict[str, str]: """5 & 6. An object rating the candidate's match for each specific criterion.""" - return self.analysis_data.get('criteria_checklist', {}) + return self.analysis_data.get("criteria_checklist", {}) @property def professional_category(self) -> str: """7. The most fitting professional field or category for the individual.""" - return self.analysis_data.get('category', 'N/A') + return self.analysis_data.get("category", "N/A") @property def language_fluency(self) -> List[Dict[str, str]]: """12. A list of languages and their fluency levels mentioned.""" - return self.analysis_data.get('language_fluency', []) - - # --- Properties for Summaries and Narrative --- + return self.analysis_data.get("language_fluency", []) @property def strengths(self) -> str: """2. A brief summary of why the candidate is a strong fit.""" - return self.analysis_data.get('strengths', '') + return self.analysis_data.get("strengths", "") @property def weaknesses(self) -> str: """3. A brief summary of where the candidate falls short or what criteria are missing.""" - return self.analysis_data.get('weaknesses', '') + return self.analysis_data.get("weaknesses", "") @property def job_fit_narrative(self) -> str: """11. A single, concise sentence summarizing the core fit.""" - return self.analysis_data.get('job_fit_narrative', '') + return self.analysis_data.get("job_fit_narrative", "") @property def recommendation(self) -> str: """9. Provide a detailed final recommendation for the candidate.""" - # Using a more descriptive name to avoid conflict with potential built-in methods - return self.analysis_data.get('recommendation', '') + return self.analysis_data.get("recommendation", "") - @property - def name(self): - return f"{self.first_name} {self.last_name}" - - @property - def full_name(self): - return self.name - - @property - def get_file_size(self): - if self.resume: - return self.resume.size - return 0 - - - def save(self, *args, **kwargs): - """Override save to ensure validation is called""" - self.clean() # Call validation before saving - super().save(*args, **kwargs) + # ==================================================================== + # 🔄 HELPER METHODS + # ==================================================================== + def set_field(self, key: str, value: Any): + """Generic method to set any single key-value pair and save.""" + self.ai_analysis_data[key] = value def get_available_stages(self): - """Get list of stages this candidate can transition to""" + """Get list of stages this application can transition to""" if not self.pk: # New record return ["Applied"] old_stage = self.__class__.objects.get(pk=self.pk).stage return self.STAGE_SEQUENCE.get(old_stage, []) + def save(self, *args, **kwargs): + """Override save to ensure validation is called""" + self.clean() # Call validation before saving + super().save(*args, **kwargs) + + # ==================================================================== + # 📋 LEGACY COMPATIBILITY PROPERTIES + # ==================================================================== + # These properties maintain compatibility with existing code that expects Candidate model + @property + def first_name(self): + """Legacy compatibility - delegates to person.first_name""" + return self.person.first_name + + @property + def last_name(self): + """Legacy compatibility - delegates to person.last_name""" + return self.person.last_name + + @property + def email(self): + """Legacy compatibility - delegates to person.email""" + return self.person.email + + @property + def phone(self): + """Legacy compatibility - delegates to person.phone if available""" + return self.person.phone or "" + + @property + def address(self): + """Legacy compatibility - delegates to person.address if available""" + return self.person.address or "" + + @property + def name(self): + """Legacy compatibility - delegates to person.full_name""" + return self.person.full_name + + @property + def full_name(self): + """Legacy compatibility - delegates to person.full_name""" + return self.person.full_name + + @property + def get_file_size(self): + """Legacy compatibility - returns resume file size""" + if self.resume: + return self.resume.size + return 0 + @property def submission(self): + """Legacy compatibility - get form submission for this application""" return FormSubmission.objects.filter(template__job=self.job).first() + @property def responses(self): + """Legacy compatibility - get form responses for this application""" if self.submission: return self.submission.responses.all() return [] - def __str__(self): - return self.full_name @property def get_meetings(self): + """Legacy compatibility - get scheduled interviews for this application""" return self.scheduled_interviews.all() + @property def get_latest_meeting(self): - schedule = self.scheduled_interviews.order_by('-created_at').first() + """Legacy compatibility - get latest meeting for this application""" + schedule = self.scheduled_interviews.order_by("-created_at").first() if schedule: return schedule.zoom_meeting return None @property def has_future_meeting(self): - """ - Checks if the candidate has any scheduled interviews for a future date/time. - """ - # Ensure timezone.now() is used for comparison + """Legacy compatibility - check for future meetings""" now = timezone.now() - # Check if any related ScheduledInterview has a future interview_date and interview_time - # We need to combine date and time for a proper datetime comparison if they are separate fields - future_meetings = self.scheduled_interviews.filter( - interview_date__gt=now.date() - ).filter( - interview_time__gte=now.time() - ).exists() - - # Also check for interviews happening later today + future_meetings = ( + self.scheduled_interviews.filter(interview_date__gt=now.date()) + .filter(interview_time__gte=now.time()) + .exists() + ) today_future_meetings = self.scheduled_interviews.filter( - interview_date=now.date(), - interview_time__gte=now.time() + interview_date=now.date(), interview_time__gte=now.time() ).exists() - return future_meetings or today_future_meetings - + @property def scoring_timeout(self): + """Legacy compatibility - check scoring timeout""" return timezone.now() <= (self.created_at + timezone.timedelta(minutes=5)) - + @property def get_interview_date(self): - if hasattr(self, 'scheduled_interview') and self.scheduled_interview: - return self.scheduled_interviews.first().interview_date + """Legacy compatibility - get interview date""" + if hasattr(self, "scheduled_interview") and self.scheduled_interview: + return self.scheduled_interviews.first().interview_date return None - - - - - + @property def get_interview_time(self): - if hasattr(self, 'scheduled_interview') and self.scheduled_interview: - return self.scheduled_interviews.first().interview_time - return None - - + """Legacy compatibility - get interview time""" + if hasattr(self, "scheduled_interview") and self.scheduled_interview: + return self.scheduled_interviews.first().interview_time + return None + @property def time_to_hire_days(self): + """Legacy compatibility - calculate time to hire""" if self.hired_date and self.created_at: time_to_hire = self.hired_date - self.created_at.date() return time_to_hire.days return 0 - + @property - def belong_to_an_agency(self): - return self.hiring_source=='Agency' - + def documents(self): + """Return all documents associated with this Application""" + from django.contrib.contenttypes.models import ContentType + content_type = ContentType.objects.get_for_model(self.__class__) + return Document.objects.filter(content_type=content_type, object_id=self.id) class TrainingMaterial(Base): title = models.CharField(max_length=255, verbose_name=_("Title")) - content = CKEditor5Field(blank=True, verbose_name=_("Content"),config_name='extends') + content = CKEditor5Field( + blank=True, verbose_name=_("Content"), config_name="extends" + ) video_link = models.URLField(blank=True, verbose_name=_("Video Link")) file = models.FileField( upload_to="training_materials/", blank=True, verbose_name=_("File") @@ -758,14 +993,19 @@ class ZoomMeeting(Base): WAITING = "waiting", _("Waiting") STARTED = "started", _("Started") ENDED = "ended", _("Ended") - CANCELLED = "cancelled",_("Cancelled") + CANCELLED = "cancelled", _("Cancelled") + # Basic meeting details topic = models.CharField(max_length=255, verbose_name=_("Topic")) meeting_id = models.CharField( - db_index=True, max_length=20, unique=True, verbose_name=_("Meeting ID") # Added index - + db_index=True, + max_length=20, + unique=True, + verbose_name=_("Meeting ID"), # Added index ) # Unique identifier for the meeting - start_time = models.DateTimeField(db_index=True, verbose_name=_("Start Time")) # Added index + start_time = models.DateTimeField( + db_index=True, verbose_name=_("Start Time") + ) # Added index duration = models.PositiveIntegerField( verbose_name=_("Duration") ) # Duration in minutes @@ -791,7 +1031,8 @@ class ZoomMeeting(Base): blank=True, null=True, verbose_name=_("Zoom Gateway Response") ) status = models.CharField( - db_index=True, max_length=20, # Added index + db_index=True, + max_length=20, # Added index null=True, blank=True, verbose_name=_("Status"), @@ -802,44 +1043,49 @@ class ZoomMeeting(Base): def __str__(self): return self.topic @property + def get_job(self): return self.interview.job + @property def get_candidate(self): - return self.interview.candidate + return self.interview.application.person + @property + def candidate_full_name(self): + return self.interview.application.person.full_name + @property def get_participants(self): - return self.interview.participants.all() + return self.interview.job.participants.all() + @property def get_users(self): - return self.interview.system_users.all() + return self.interview.job.users.all() class MeetingComment(Base): """ Model for storing meeting comments/notes """ + meeting = models.ForeignKey( ZoomMeeting, on_delete=models.CASCADE, related_name="comments", - verbose_name=_("Meeting") + verbose_name=_("Meeting"), ) author = models.ForeignKey( User, on_delete=models.CASCADE, related_name="meeting_comments", - verbose_name=_("Author") - ) - content = CKEditor5Field( - verbose_name=_("Content"), - config_name='extends' + verbose_name=_("Author"), ) + content = CKEditor5Field(verbose_name=_("Content"), config_name="extends") # Inherited from Base: created_at, updated_at, slug class Meta: verbose_name = _("Meeting Comment") verbose_name_plural = _("Meeting Comments") - ordering = ['-created_at'] + ordering = ["-created_at"] def __str__(self): return f"Comment by {self.author.get_username()} on {self.meeting.topic}" @@ -851,14 +1097,22 @@ class FormTemplate(Base): """ job = models.OneToOneField( - JobPosting, on_delete=models.CASCADE, related_name="form_template", db_index=True + JobPosting, + on_delete=models.CASCADE, + related_name="form_template", + db_index=True, ) name = models.CharField(max_length=200, help_text="Name of the form template") description = models.TextField( blank=True, help_text="Description of the form template" ) created_by = models.ForeignKey( - User, on_delete=models.CASCADE, related_name="form_templates",null=True,blank=True, db_index=True + User, + on_delete=models.CASCADE, + related_name="form_templates", + null=True, + blank=True, + db_index=True, ) is_active = models.BooleanField( default=False, help_text="Whether this template is active" @@ -869,8 +1123,8 @@ class FormTemplate(Base): verbose_name = "Form Template" verbose_name_plural = "Form Templates" indexes = [ - models.Index(fields=['created_at']), - models.Index(fields=['is_active']), + models.Index(fields=["created_at"]), + models.Index(fields=["is_active"]), ] def __str__(self): @@ -1021,7 +1275,10 @@ class FormSubmission(Base): """ template = models.ForeignKey( - FormTemplate, on_delete=models.CASCADE, related_name="submissions", db_index=True + FormTemplate, + on_delete=models.CASCADE, + related_name="submissions", + db_index=True, ) submitted_by = models.ForeignKey( User, @@ -1029,20 +1286,22 @@ class FormSubmission(Base): null=True, blank=True, related_name="form_submissions", - db_index=True + db_index=True, ) - submitted_at = models.DateTimeField(db_index=True, auto_now_add=True) # Added index + submitted_at = models.DateTimeField(db_index=True, auto_now_add=True) # Added index applicant_name = models.CharField( max_length=200, blank=True, help_text="Name of the applicant" ) - applicant_email = models.EmailField(db_index=True, blank=True, help_text="Email of the applicant") # Added index + applicant_email = models.EmailField( + db_index=True, blank=True, help_text="Email of the applicant" + ) # Added index class Meta: ordering = ["-submitted_at"] verbose_name = "Form Submission" verbose_name_plural = "Form Submissions" indexes = [ - models.Index(fields=['submitted_at']), + models.Index(fields=["submitted_at"]), ] def __str__(self): @@ -1055,7 +1314,10 @@ class FieldResponse(Base): """ submission = models.ForeignKey( - FormSubmission, on_delete=models.CASCADE, related_name="responses", db_index=True + FormSubmission, + on_delete=models.CASCADE, + related_name="responses", + db_index=True, ) field = models.ForeignKey( FormField, on_delete=models.CASCADE, related_name="responses", db_index=True @@ -1073,8 +1335,8 @@ class FieldResponse(Base): verbose_name = "Field Response" verbose_name_plural = "Field Responses" indexes = [ - models.Index(fields=['submission']), - models.Index(fields=['field']), + models.Index(fields=["submission"]), + models.Index(fields=["field"]), ] def __str__(self): @@ -1320,6 +1582,14 @@ class IntegrationLog(Base): class HiringAgency(Base): + user = models.OneToOneField( + User, + on_delete=models.CASCADE, + related_name="agency_profile", + verbose_name=_("User"), + null=True, + blank=True, + ) name = models.CharField(max_length=200, unique=True, verbose_name=_("Agency Name")) contact_person = models.CharField( max_length=150, blank=True, verbose_name=_("Contact Person") @@ -1353,31 +1623,33 @@ class AgencyJobAssignment(Base): HiringAgency, on_delete=models.CASCADE, related_name="job_assignments", - verbose_name=_("Agency") + verbose_name=_("Agency"), ) job = models.ForeignKey( JobPosting, on_delete=models.CASCADE, related_name="agency_assignments", - verbose_name=_("Job") + verbose_name=_("Job"), ) # Limits & Controls max_candidates = models.PositiveIntegerField( verbose_name=_("Maximum Candidates"), - help_text=_("Maximum candidates agency can submit for this job") + help_text=_("Maximum candidates agency can submit for this job"), ) candidates_submitted = models.PositiveIntegerField( default=0, verbose_name=_("Candidates Submitted"), - help_text=_("Number of candidates submitted so far") + help_text=_("Number of candidates submitted so far"), ) # Timeline - assigned_date = models.DateTimeField(auto_now_add=True, verbose_name=_("Assigned Date")) + assigned_date = models.DateTimeField( + auto_now_add=True, verbose_name=_("Assigned Date") + ) deadline_date = models.DateTimeField( verbose_name=_("Deadline Date"), - help_text=_("Deadline for agency to submit candidates") + help_text=_("Deadline for agency to submit candidates"), ) # Status & Extensions @@ -1386,26 +1658,25 @@ class AgencyJobAssignment(Base): max_length=20, choices=AssignmentStatus.choices, default=AssignmentStatus.ACTIVE, - verbose_name=_("Status") + verbose_name=_("Status"), ) # Extension tracking deadline_extended = models.BooleanField( - default=False, - verbose_name=_("Deadline Extended") + default=False, verbose_name=_("Deadline Extended") ) original_deadline = models.DateTimeField( null=True, blank=True, verbose_name=_("Original Deadline"), - help_text=_("Original deadline before extensions") + help_text=_("Original deadline before extensions"), ) # Admin notes admin_notes = models.TextField( blank=True, verbose_name=_("Admin Notes"), - help_text=_("Internal notes about this assignment") + help_text=_("Internal notes about this assignment"), ) class Meta: @@ -1413,12 +1684,12 @@ class AgencyJobAssignment(Base): verbose_name_plural = _("Agency Job Assignments") ordering = ["-created_at"] indexes = [ - models.Index(fields=['agency', 'status']), - models.Index(fields=['job', 'status']), - models.Index(fields=['deadline_date']), - models.Index(fields=['is_active']), + models.Index(fields=["agency", "status"]), + models.Index(fields=["job", "status"]), + models.Index(fields=["deadline_date"]), + models.Index(fields=["is_active"]), ] - unique_together = ['agency', 'job'] # Prevent duplicate assignments + unique_together = ["agency", "job"] # Prevent duplicate assignments def __str__(self): return f"{self.agency.name} - {self.job.title}" @@ -1435,10 +1706,10 @@ class AgencyJobAssignment(Base): def is_currently_active(self): """Check if assignment is currently active""" return ( - self.status == 'ACTIVE' and - self.deadline_date and - self.deadline_date > timezone.now() and - self.candidates_submitted < self.max_candidates + self.status == "ACTIVE" + and self.deadline_date + and self.deadline_date > timezone.now() + and self.candidates_submitted < self.max_candidates ) @property @@ -1455,7 +1726,9 @@ class AgencyJobAssignment(Base): raise ValidationError(_("Maximum candidates must be greater than 0")) if self.candidates_submitted > self.max_candidates: - raise ValidationError(_("Candidates submitted cannot exceed maximum candidates")) + raise ValidationError( + _("Candidates submitted cannot exceed maximum candidates") + ) @property def remaining_slots(self): @@ -1475,16 +1748,18 @@ class AgencyJobAssignment(Base): @property def can_submit(self): """Check if agency can still submit candidates""" - return (self.is_active and - not self.is_expired and - not self.is_full and - self.status == self.AssignmentStatus.ACTIVE) + return ( + self.is_active + and not self.is_expired + and not self.is_full + and self.status == self.AssignmentStatus.ACTIVE + ) def increment_submission_count(self): """Safely increment the submitted candidates count""" if self.can_submit: self.candidates_submitted += 1 - self.save(update_fields=['candidates_submitted']) + self.save(update_fields=["candidates_submitted"]) # Check if assignment is now complete # if self.candidates_submitted >= self.max_candidates: @@ -1496,13 +1771,23 @@ class AgencyJobAssignment(Base): def extend_deadline(self, new_deadline): """Extend the deadline for this assignment""" # Convert database deadline to timezone-aware for comparison - deadline_aware = timezone.make_aware(self.deadline_date) if timezone.is_naive(self.deadline_date) else self.deadline_date + deadline_aware = ( + timezone.make_aware(self.deadline_date) + if timezone.is_naive(self.deadline_date) + else self.deadline_date + ) if new_deadline > deadline_aware: if not self.deadline_extended: self.original_deadline = self.deadline_date self.deadline_extended = True self.deadline_date = new_deadline - self.save(update_fields=['deadline_date', 'original_deadline', 'deadline_extended']) + self.save( + update_fields=[ + "deadline_date", + "original_deadline", + "deadline_extended", + ] + ) return True return False @@ -1514,52 +1799,42 @@ class AgencyAccessLink(Base): AgencyJobAssignment, on_delete=models.CASCADE, related_name="access_link", - verbose_name=_("Assignment") + verbose_name=_("Assignment"), ) # Security unique_token = models.CharField( - max_length=64, - unique=True, - editable=False, - verbose_name=_("Unique Token") + max_length=64, unique=True, editable=False, verbose_name=_("Unique Token") ) access_password = models.CharField( max_length=32, verbose_name=_("Access Password"), - help_text=_("Password for agency access") + help_text=_("Password for agency access"), ) # Timeline created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Created At")) expires_at = models.DateTimeField( - verbose_name=_("Expires At"), - help_text=_("When this access link expires") + verbose_name=_("Expires At"), help_text=_("When this access link expires") ) last_accessed = models.DateTimeField( - null=True, - blank=True, - verbose_name=_("Last Accessed") + null=True, blank=True, verbose_name=_("Last Accessed") ) # Usage tracking access_count = models.PositiveIntegerField( - default=0, - verbose_name=_("Access Count") - ) - is_active = models.BooleanField( - default=True, - verbose_name=_("Is Active") + default=0, verbose_name=_("Access Count") ) + is_active = models.BooleanField(default=True, verbose_name=_("Is Active")) class Meta: verbose_name = _("Agency Access Link") verbose_name_plural = _("Agency Access Links") ordering = ["-created_at"] indexes = [ - models.Index(fields=['unique_token']), - models.Index(fields=['expires_at']), - models.Index(fields=['is_active']), + models.Index(fields=["unique_token"]), + models.Index(fields=["expires_at"]), + models.Index(fields=["is_active"]), ] def __str__(self): @@ -1584,19 +1859,21 @@ class AgencyAccessLink(Base): """Record an access to this link""" self.last_accessed = timezone.now() self.access_count += 1 - self.save(update_fields=['last_accessed', 'access_count']) + self.save(update_fields=["last_accessed", "access_count"]) def generate_token(self): """Generate a unique secure token""" import secrets + self.unique_token = secrets.token_urlsafe(48) def generate_password(self): """Generate a random password""" import secrets import string + alphabet = string.ascii_letters + string.digits - self.access_password = ''.join(secrets.choice(alphabet) for _ in range(12)) + self.access_password = "".join(secrets.choice(alphabet) for _ in range(12)) def save(self, *args, **kwargs): """Override save to generate token and password if not set""" @@ -1607,8 +1884,6 @@ class AgencyAccessLink(Base): super().save(*args, **kwargs) - - class BreakTime(models.Model): """Model to store break times for a schedule""" @@ -1621,34 +1896,45 @@ class BreakTime(models.Model): class InterviewSchedule(Base): """Stores the scheduling criteria for interviews""" - """Stores individual scheduled interviews""" class InterviewType(models.TextChoices): - REMOTE = 'Remote', 'Remote Interview' - ONSITE = 'Onsite', 'In-Person Interview' - + REMOTE = 'Remote', 'Remote Interview' + ONSITE = 'Onsite', 'In-Person Interview' + interview_type = models.CharField( - max_length=10, + max_length=10, choices=InterviewType.choices, default=InterviewType.REMOTE, verbose_name="Interview Meeting Type" ) - job = models.ForeignKey( - JobPosting, on_delete=models.CASCADE, related_name="interview_schedules", db_index=True + JobPosting, + on_delete=models.CASCADE, + related_name="interview_schedules", + db_index=True, ) - candidates = models.ManyToManyField(Candidate, related_name="interview_schedules", blank=True,null=True) - start_date = models.DateField(db_index=True, verbose_name=_("Start Date")) # Added index - end_date = models.DateField(db_index=True, verbose_name=_("End Date")) # Added index + applications = models.ManyToManyField( + Application, related_name="interview_schedules", blank=True, null=True + ) + start_date = models.DateField( + db_index=True, verbose_name=_("Start Date") + ) # Added index + end_date = models.DateField( + db_index=True, verbose_name=_("End Date") + ) # Added index working_days = models.JSONField( verbose_name=_("Working Days") ) # Store days of week as [0,1,2,3,4] for Mon-Fri start_time = models.TimeField(verbose_name=_("Start Time")) end_time = models.TimeField(verbose_name=_("End Time")) - break_start_time = models.TimeField(verbose_name=_("Break Start Time"),null=True,blank=True) - break_end_time = models.TimeField(verbose_name=_("Break End Time"),null=True,blank=True) + break_start_time = models.TimeField( + verbose_name=_("Break Start Time"), null=True, blank=True + ) + break_end_time = models.TimeField( + verbose_name=_("Break End Time"), null=True, blank=True + ) interview_duration = models.PositiveIntegerField( verbose_name=_("Interview Duration (minutes)") @@ -1656,30 +1942,31 @@ class InterviewSchedule(Base): buffer_time = models.PositiveIntegerField( verbose_name=_("Buffer Time (minutes)"), default=0 ) - created_by = models.ForeignKey(User, on_delete=models.CASCADE, db_index=True) # Added index + created_by = models.ForeignKey( + User, on_delete=models.CASCADE, db_index=True + ) # Added index def __str__(self): return f"Interview Schedule for {self.job.title}" class Meta: indexes = [ - models.Index(fields=['start_date']), - models.Index(fields=['end_date']), - models.Index(fields=['created_by']), + models.Index(fields=["start_date"]), + models.Index(fields=["end_date"]), + models.Index(fields=["created_by"]), ] - class ScheduledInterview(Base): - - #for one candidate - candidate = models.ForeignKey( - Candidate, + """Stores individual scheduled interviews""" + + application = models.ForeignKey( + Application, on_delete=models.CASCADE, related_name="scheduled_interviews", - db_index=True + db_index=True, ) - + participants = models.ManyToManyField('Participants', blank=True) @@ -1687,25 +1974,36 @@ class ScheduledInterview(Base): job = models.ForeignKey( - "JobPosting", on_delete=models.CASCADE, related_name="scheduled_interviews", db_index=True + "JobPosting", + on_delete=models.CASCADE, + related_name="scheduled_interviews", + db_index=True, ) zoom_meeting = models.OneToOneField( ZoomMeeting, on_delete=models.CASCADE, related_name="interview", db_index=True, null=True, blank=True ) - + onsite_meeting= models.OneToOneField( OnsiteMeeting, on_delete=models.CASCADE, related_name="onsite_interview", db_index=True, null=True, blank=True ) schedule = models.ForeignKey( - InterviewSchedule, on_delete=models.CASCADE, related_name="interviews",null=True,blank=True, db_index=True + InterviewSchedule, + on_delete=models.CASCADE, + related_name="interviews", + null=True, + blank=True, + db_index=True, ) - interview_date = models.DateField(db_index=True, verbose_name=_("Interview Date")) # Added index + interview_date = models.DateField( + db_index=True, verbose_name=_("Interview Date") + ) # Added index interview_time = models.TimeField(verbose_name=_("Interview Time")) status = models.CharField( - db_index=True, max_length=20, # Added index + db_index=True, + max_length=20, # Added index choices=[ ("scheduled", _("Scheduled")), ("confirmed", _("Confirmed")), @@ -1718,22 +2016,24 @@ class ScheduledInterview(Base): updated_at = models.DateTimeField(auto_now=True) def __str__(self): - return f"Interview with {self.candidate.name} for {self.job.title}" + return f"Interview with {self.application.person.full_name} for {self.job.title}" class Meta: indexes = [ - models.Index(fields=['job', 'status']), - models.Index(fields=['interview_date', 'interview_time']), - models.Index(fields=['candidate', 'job']), + models.Index(fields=["job", "status"]), + models.Index(fields=["interview_date", "interview_time"]), + models.Index(fields=["application", "job"]), ] + class Notification(models.Model): """ Model to store system notifications, primarily for emails. """ + class NotificationType(models.TextChoices): EMAIL = "email", _("Email") - IN_APP = "in_app", _("In-App") # For future expansion + IN_APP = "in_app", _("In-App") # For future expansion class Status(models.TextChoices): PENDING = "pending", _("Pending") @@ -1746,20 +2046,20 @@ class Notification(models.Model): User, on_delete=models.CASCADE, related_name="notifications", - verbose_name=_("Recipient") + verbose_name=_("Recipient"), ) message = models.TextField(verbose_name=_("Notification Message")) notification_type = models.CharField( max_length=20, choices=NotificationType.choices, default=NotificationType.EMAIL, - verbose_name=_("Notification Type") + verbose_name=_("Notification Type"), ) status = models.CharField( max_length=20, choices=Status.choices, default=Status.PENDING, - verbose_name=_("Status") + verbose_name=_("Status"), ) related_meeting = models.ForeignKey( ZoomMeeting, @@ -1767,11 +2067,11 @@ class Notification(models.Model): related_name="notifications", null=True, blank=True, - verbose_name=_("Related Meeting") + verbose_name=_("Related Meeting"), ) scheduled_for = models.DateTimeField( verbose_name=_("Scheduled Send Time"), - help_text=_("The date and time this notification is scheduled to be sent.") + help_text=_("The date and time this notification is scheduled to be sent."), ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -1783,8 +2083,8 @@ class Notification(models.Model): verbose_name = _("Notification") verbose_name_plural = _("Notifications") indexes = [ - models.Index(fields=['status', 'scheduled_for']), - models.Index(fields=['recipient']), + models.Index(fields=["status", "scheduled_for"]), + models.Index(fields=["recipient"]), ] def __str__(self): @@ -1793,28 +2093,242 @@ class Notification(models.Model): def mark_as_sent(self): self.status = Notification.Status.SENT self.last_error = "" - self.save(update_fields=['status', 'last_error']) + self.save(update_fields=["status", "last_error"]) def mark_as_failed(self, error_message=""): self.status = Notification.Status.FAILED self.last_error = error_message self.attempts += 1 - self.save(update_fields=['status', 'last_error', 'attempts']) - + self.save(update_fields=["status", "last_error", "attempts"]) class Participants(Base): """Model to store Participants details""" - name = models.CharField(max_length=255, verbose_name=_("Participant Name"),null=True,blank=True) - email= models.EmailField(verbose_name=_("Email")) - phone = models.CharField(max_length=12,verbose_name=_("Phone Number"),null=True,blank=True) + name = models.CharField( + max_length=255, verbose_name=_("Participant Name"), null=True, blank=True + ) + email = models.EmailField(verbose_name=_("Email")) + phone = models.CharField( + max_length=12, verbose_name=_("Phone Number"), null=True, blank=True + ) designation = models.CharField( - max_length=100, blank=True, verbose_name=_("Designation"),null=True + max_length=100, blank=True, verbose_name=_("Designation"), null=True ) def __str__(self): return f"{self.name} - {self.email}" +class Message(Base): + """Model for messaging between different user types""" + class MessageType(models.TextChoices): + DIRECT = "direct", _("Direct Message") + JOB_RELATED = "job_related", _("Job Related") + SYSTEM = "system", _("System Notification") + + sender = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name="sent_messages", + verbose_name=_("Sender"), + ) + recipient = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name="received_messages", + null=True, + blank=True, + verbose_name=_("Recipient"), + ) + job = models.ForeignKey( + JobPosting, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="messages", + verbose_name=_("Related Job"), + ) + subject = models.CharField(max_length=200, verbose_name=_("Subject")) + content = models.TextField(verbose_name=_("Message Content")) + message_type = models.CharField( + max_length=20, + choices=MessageType.choices, + default=MessageType.DIRECT, + verbose_name=_("Message Type"), + ) + is_read = models.BooleanField(default=False, verbose_name=_("Is Read")) + read_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Read At")) + + class Meta: + verbose_name = _("Message") + verbose_name_plural = _("Messages") + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["sender", "created_at"]), + models.Index(fields=["recipient", "is_read", "created_at"]), + models.Index(fields=["job", "created_at"]), + models.Index(fields=["message_type", "created_at"]), + ] + + def __str__(self): + return f"Message from {self.sender.get_username()} to {self.recipient.get_username() if self.recipient else 'N/A'}" + + def mark_as_read(self): + """Mark message as read and set read timestamp""" + if not self.is_read: + self.is_read = True + self.read_at = timezone.now() + self.save(update_fields=["is_read", "read_at"]) + + @property + def is_job_related(self): + """Check if message is related to a job""" + return self.job is not None + + def get_auto_recipient(self): + """Get auto recipient based on job assignment""" + if self.job and self.job.assigned_to: + return self.job.assigned_to + return None + + def clean(self): + """Validate message constraints""" + super().clean() + + # For job-related messages, ensure recipient is assigned to the job + if self.job and not self.recipient: + if self.job.assigned_to: + self.recipient = self.job.assigned_to + else: + raise ValidationError(_("Job is not assigned to any user. Please assign the job first.")) + + # Validate sender can message this recipient based on user types + # if self.sender and self.recipient: + # self._validate_messaging_permissions() + + def _validate_messaging_permissions(self): + """Validate if sender can message recipient based on user types""" + sender_type = self.sender.user_type + recipient_type = self.recipient.user_type + + # Staff can message anyone + if sender_type == "staff": + return + + # Agency users can only message staff or their own candidates + if sender_type == "agency": + if recipient_type not in ["staff", "candidate"]: + raise ValidationError(_("Agencies can only message staff or candidates.")) + + # If messaging a candidate, ensure candidate is from their agency + if recipient_type == "candidate" and self.job: + if not self.job.hiring_agency.filter(user=self.sender).exists(): + raise ValidationError(_("You can only message candidates from your assigned jobs.")) + + # Candidate users can only message staff + if sender_type == "candidate": + if recipient_type != "staff": + raise ValidationError(_("Candidates can only message staff.")) + + # If job-related, ensure candidate applied for the job + if self.job: + if not Candidate.objects.filter(job=self.job, user=self.sender).exists(): + raise ValidationError(_("You can only message about jobs you have applied for.")) + + def save(self, *args, **kwargs): + """Override save to handle auto-recipient logic""" + self.clean() + super().save(*args, **kwargs) + + +class Document(Base): + """Model for storing documents using Generic Foreign Key""" + + class DocumentType(models.TextChoices): + RESUME = "resume", _("Resume") + COVER_LETTER = "cover_letter", _("Cover Letter") + CERTIFICATE = "certificate", _("Certificate") + ID_DOCUMENT = "id_document", _("ID Document") + PASSPORT = "passport", _("Passport") + EDUCATION = "education", _("Education Document") + EXPERIENCE = "experience", _("Experience Letter") + OTHER = "other", _("Other") + + # Generic Foreign Key fields + content_type = models.ForeignKey( + ContentType, + on_delete=models.CASCADE, + verbose_name=_("Content Type"), + ) + object_id = models.PositiveIntegerField( + verbose_name=_("Object ID"), + ) + content_object = GenericForeignKey('content_type', 'object_id') + + file = models.FileField( + upload_to="documents/%Y/%m/", + verbose_name=_("Document File"), + validators=[validate_image_size], + ) + document_type = models.CharField( + max_length=20, + choices=DocumentType.choices, + default=DocumentType.OTHER, + verbose_name=_("Document Type"), + ) + description = models.CharField( + max_length=200, + blank=True, + verbose_name=_("Description"), + ) + uploaded_by = models.ForeignKey( + User, + on_delete=models.SET_NULL, + null=True, + blank=True, + verbose_name=_("Uploaded By"), + ) + + class Meta: + verbose_name = _("Document") + verbose_name_plural = _("Documents") + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["content_type", "object_id", "document_type", "created_at"]), + ] + + def __str__(self): + try: + if hasattr(self.content_object, 'full_name'): + object_name = self.content_object.full_name + elif hasattr(self.content_object, 'title'): + object_name = self.content_object.title + elif hasattr(self.content_object, '__str__'): + object_name = str(self.content_object) + else: + object_name = f"Object {self.object_id}" + return f"{self.get_document_type_display()} - {object_name}" + except: + return f"{self.get_document_type_display()} - {self.object_id}" + + @property + def file_size(self): + """Return file size in human readable format""" + if self.file: + size = self.file.size + if size < 1024: + return f"{size} bytes" + elif size < 1024 * 1024: + return f"{size / 1024:.1f} KB" + else: + return f"{size / (1024 * 1024):.1f} MB" + return "0 bytes" + + @property + def file_extension(self): + """Return file extension""" + if self.file: + return self.file.name.split('.')[-1].upper() + return "" diff --git a/recruitment/serializers.py b/recruitment/serializers.py index ea52220..6387523 100644 --- a/recruitment/serializers.py +++ b/recruitment/serializers.py @@ -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__' diff --git a/recruitment/signals.py b/recruitment/signals.py index ba2c033..98e12fc 100644 --- a/recruitment/signals.py +++ b/recruitment/signals.py @@ -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() \ No newline at end of file + 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() \ No newline at end of file diff --git a/recruitment/tasks.py b/recruitment/tasks.py index 4515e1b..a0c05cf 100644 --- a/recruitment/tasks.py +++ b/recruitment/tasks.py @@ -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} diff --git a/recruitment/templatetags/file_filters.py b/recruitment/templatetags/file_filters.py new file mode 100644 index 0000000..4ed1701 --- /dev/null +++ b/recruitment/templatetags/file_filters.py @@ -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) diff --git a/recruitment/tests.py b/recruitment/tests.py index 20feb89..afadf48 100644 --- a/recruitment/tests.py +++ b/recruitment/tests.py @@ -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) diff --git a/recruitment/urls.py b/recruitment/urls.py index 794e2c5..394687b 100644 --- a/recruitment/urls.py +++ b/recruitment/urls.py @@ -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//upload_image_simple/', views.job_image_upload, name='job_image_upload'), - path('jobs//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//", views.PersonDetailView.as_view(), name="person_detail"), + path("persons//update/", views.PersonUpdateView.as_view(), name="person_update"), + path("persons//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//upload_image_simple/", + views.job_image_upload, + name="job_image_upload", + ), + path("jobs//update/", views.edit_job, name="job_update"), # path('jobs//delete/', views., name='job_delete'), path('jobs//', views.job_detail, name='job_detail'), path('jobs//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//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//schedule-interviews/', views.schedule_interviews_view, name='schedule_interviews'), - path('jobs//confirm-schedule-interviews/', views.confirm_schedule_interviews_view, name='confirm_schedule_interviews_view'), + path( + "jobs//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//schedule-interviews/", + views.schedule_interviews_view, + name="schedule_interviews", + ), + path( + "jobs//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//', views_frontend.CandidateCreateView.as_view(), name='candidate_create_for_job'), - path('jobs//candidates/', views_frontend.JobCandidatesListView.as_view(), name='job_candidates_list'), - path('candidates//update/', views_frontend.CandidateUpdateView.as_view(), name='candidate_update'), - path('candidates//delete/', views_frontend.CandidateDeleteView.as_view(), name='candidate_delete'), - path('candidate//view/', views_frontend.candidate_detail, name='candidate_detail'), - path('candidate//resume-template/', views_frontend.candidate_resume_template_view, name='candidate_resume_template'), - path('candidate//update-stage/', views_frontend.candidate_update_stage, name='candidate_update_stage'), - path('candidate//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//", + views_frontend.ApplicationCreateView.as_view(), + name="candidate_create_for_job", + ), + path( + "jobs//candidates/", + views_frontend.JobApplicationListView.as_view(), + name="job_candidates_list", + ), + path( + "candidates//update/", + views_frontend.ApplicationUpdateView.as_view(), + name="candidate_update", + ), + path( + "candidates//delete/", + views_frontend.ApplicationDeleteView.as_view(), + name="candidate_delete", + ), + path( + "candidate//view/", + views_frontend.candidate_detail, + name="candidate_detail", + ), + path( + "candidate//resume-template/", + views_frontend.candidate_resume_template_view, + name="candidate_resume_template", + ), + path( + "candidate//update-stage/", + views_frontend.candidate_update_stage, + name="candidate_update_stage", + ), + path( + "candidate//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//', views_frontend.TrainingDetailView.as_view(), name='training_detail'), - path('training//update/', views_frontend.TrainingUpdateView.as_view(), name='training_update'), - path('training//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//", + views_frontend.TrainingDetailView.as_view(), + name="training_detail", + ), + path( + "training//update/", + views_frontend.TrainingUpdateView.as_view(), + name="training_update", + ), + path( + "training//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//', views.ZoomMeetingDetailsView.as_view(), name='meeting_details'), - path('meetings/update-meeting//', views.ZoomMeetingUpdateView.as_view(), name='update_meeting'), - path('meetings/delete-meeting//', 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//", + views.ZoomMeetingDetailsView.as_view(), + name="meeting_details", + ), + path( + "meetings/update-meeting//", + views.ZoomMeetingUpdateView.as_view(), + name="update_meeting", + ), + path( + "meetings/delete-meeting//", + views.ZoomMeetingDeleteView, + name="delete_meeting", + ), # JobPosting functional views URLs (keeping for compatibility) - path('api/create/', views.create_job, name='create_job_api'), - path('api//edit/', views.edit_job, name='edit_job_api'), - + path("api/create/", views.create_job, name="create_job_api"), + path("api//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//', 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//edit_linkedin_post_content/',views.edit_linkedin_post_content,name='edit_linkedin_post_content'), - path('jobs//candidate_screening_view/', views.candidate_screening_view, name='candidate_screening_view'), - path('jobs//candidate_exam_view/', views.candidate_exam_view, name='candidate_exam_view'), - path('jobs//candidate_interview_view/', views.candidate_interview_view, name='candidate_interview_view'), - path('jobs//candidate_offer_view/', views_frontend.candidate_offer_view, name='candidate_offer_view'), - path('jobs//candidate_hired_view/', views_frontend.candidate_hired_view, name='candidate_hired_view'), - path('jobs//export//csv/', views_frontend.export_candidates_csv, name='export_candidates_csv'), - path('jobs//candidates//update_status///', views_frontend.update_candidate_status, name='update_candidate_status'), - + path("forms/builder/", views.form_builder, name="form_builder"), + path( + "forms/builder//", 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//edit_linkedin_post_content/", + views.edit_linkedin_post_content, + name="edit_linkedin_post_content", + ), + path( + "jobs//candidate_screening_view/", + views.candidate_screening_view, + name="candidate_screening_view", + ), + path( + "jobs//candidate_exam_view/", + views.candidate_exam_view, + name="candidate_exam_view", + ), + path( + "jobs//candidate_interview_view/", + views.candidate_interview_view, + name="candidate_interview_view", + ), + path( + "jobs//candidate_offer_view/", + views_frontend.candidate_offer_view, + name="candidate_offer_view", + ), + path( + "jobs//candidate_hired_view/", + views_frontend.candidate_hired_view, + name="candidate_hired_view", + ), + path( + "jobs//export//csv/", + views_frontend.export_candidates_csv, + name="export_candidates_csv", + ), + path( + "jobs//candidates//update_status///", + views_frontend.update_candidate_status, + name="update_candidate_status", + ), # Sync URLs - path('jobs//sync-hired-candidates/', views_frontend.sync_hired_candidates, name='sync_hired_candidates'), - path('sources//test-connection/', views_frontend.test_source_connection, name='test_source_connection'), - - path('jobs///reschedule_meeting_for_candidate//', views.reschedule_meeting_for_candidate, name='reschedule_meeting_for_candidate'), - - path('jobs//update_candidate_exam_status/', views.update_candidate_exam_status, name='update_candidate_exam_status'), - path('jobs//bulk_update_candidate_exam_status/', views.bulk_update_candidate_exam_status, name='bulk_update_candidate_exam_status'), - - path('htmx//candidate_criteria_view/', views.candidate_criteria_view_htmx, name='candidate_criteria_view_htmx'), - path('htmx//candidate_set_exam_date/', views.candidate_set_exam_date, name='candidate_set_exam_date'), - - path('htmx//candidate_update_status/', views.candidate_update_status, name='candidate_update_status'), - + path( + "jobs//sync-hired-candidates/", + views_frontend.sync_hired_candidates, + name="sync_hired_candidates", + ), + path( + "sources//test-connection/", + views_frontend.test_source_connection, + name="test_source_connection", + ), + path( + "jobs///reschedule_meeting_for_candidate//", + views.reschedule_meeting_for_candidate, + name="reschedule_meeting_for_candidate", + ), + path( + "jobs//update_candidate_exam_status/", + views.update_candidate_exam_status, + name="update_candidate_exam_status", + ), + path( + "jobs//bulk_update_candidate_exam_status/", + views.bulk_update_candidate_exam_status, + name="bulk_update_candidate_exam_status", + ), + path( + "htmx//candidate_criteria_view/", + views.candidate_criteria_view_htmx, + name="candidate_criteria_view_htmx", + ), + path( + "htmx//candidate_set_exam_date/", + views.candidate_set_exam_date, + name="candidate_set_exam_date", + ), + path( + "htmx//candidate_update_status/", + views.candidate_update_status, + name="candidate_update_status", + ), # path('forms/form//submit/', views.submit_form, name='submit_form'), # path('forms/form//', views.form_wizard_view, name='form_wizard'), - path('forms//submissions//', views.form_submission_details, name='form_submission_details'), - path('forms/template//submissions/', views.form_template_submissions_list, name='form_template_submissions_list'), - path('forms/template//all-submissions/', views.form_template_all_submissions, name='form_template_all_submissions'), - + path( + "forms//submissions//", + views.form_submission_details, + name="form_submission_details", + ), + path( + "forms/template//submissions/", + views.form_template_submissions_list, + name="form_template_submissions_list", + ), + path( + "forms/template//all-submissions/", + views.form_template_all_submissions, + name="form_template_all_submissions", + ), # path('forms//', views.form_preview, name='form_preview'), # path('forms//submit/', views.form_submit, name='form_submit'), # path('forms//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//', views.load_form_template, name='load_form_template'), # path('api/templates//delete/', views.delete_form_template, name='delete_form_template'), - - - path('jobs//calendar/', views.interview_calendar_view, name='interview_calendar'), - path('jobs//calendar/interview//', views.interview_detail_view, name='interview_detail'), - + path( + "jobs//calendar/", + views.interview_calendar_view, + name="interview_calendar", + ), + path( + "jobs//calendar/interview//", + views.interview_detail_view, + name="interview_detail", + ), # Candidate Meeting Scheduling/Rescheduling URLs - path('jobs//candidates//schedule-meeting/', views.schedule_candidate_meeting, name='schedule_candidate_meeting'), - path('api/jobs//candidates//schedule-meeting/', views.api_schedule_candidate_meeting, name='api_schedule_candidate_meeting'), - path('jobs//candidates//reschedule-meeting//', views.reschedule_candidate_meeting, name='reschedule_candidate_meeting'), - path('api/jobs//candidates//reschedule-meeting//', views.api_reschedule_candidate_meeting, name='api_reschedule_candidate_meeting'), + path( + "jobs//candidates//schedule-meeting/", + views.schedule_candidate_meeting, + name="schedule_candidate_meeting", + ), + path( + "api/jobs//candidates//schedule-meeting/", + views.api_schedule_candidate_meeting, + name="api_schedule_candidate_meeting", + ), + path( + "jobs//candidates//reschedule-meeting//", + views.reschedule_candidate_meeting, + name="reschedule_candidate_meeting", + ), + path( + "api/jobs//candidates//reschedule-meeting//", + views.api_reschedule_candidate_meeting, + name="api_reschedule_candidate_meeting", + ), # New URL for simple page-based meeting scheduling - path('jobs//candidates//schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'), - path('jobs//candidates//delete_meeting_for_candidate//', views.delete_meeting_for_candidate, name='delete_meeting_for_candidate'), - - + path( + "jobs//candidates//schedule-meeting-page/", + views.schedule_meeting_for_candidate, + name="schedule_meeting_for_candidate", + ), + path( + "jobs//candidates//delete_meeting_for_candidate//", + views.delete_meeting_for_candidate, + name="delete_meeting_for_candidate", + ), # users urls - path('user/',views.user_detail,name='user_detail'), - path('user/user_profile_image_update/',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//',views.set_staff_password,name='set_staff_password'), - path('account_toggle_status/',views.account_toggle_status,name='account_toggle_status'), - - - + path("user/", views.user_detail, name="user_detail"), + path( + "user/user_profile_image_update/", + 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//", + views.set_staff_password, + name="set_staff_password", + ), + path( + "account_toggle_status/", + 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//', views_source.SourceDetailView.as_view(), name='source_detail'), - path('sources//update/', views_source.SourceUpdateView.as_view(), name='source_update'), - path('sources//delete/', views_source.SourceDeleteView.as_view(), name='source_delete'), - path('sources//generate-keys/', views_source.generate_api_keys_view, name='generate_api_keys'), - path('sources//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//", + views_source.SourceDetailView.as_view(), + name="source_detail", + ), + path( + "sources//update/", + views_source.SourceUpdateView.as_view(), + name="source_update", + ), + path( + "sources//delete/", + views_source.SourceDeleteView.as_view(), + name="source_delete", + ), + path( + "sources//generate-keys/", + views_source.generate_api_keys_view, + name="generate_api_keys", + ), + path( + "sources//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//comments/add/', views.add_meeting_comment, name='add_meeting_comment'), - path('meetings//comments//edit/', views.edit_meeting_comment, name='edit_meeting_comment'), - - path('meetings//comments//delete/', views.delete_meeting_comment, name='delete_meeting_comment'), - - path('meetings//set_meeting_candidate/', views.set_meeting_candidate, name='set_meeting_candidate'), - + path( + "meetings//comments/add/", + views.add_meeting_comment, + name="add_meeting_comment", + ), + path( + "meetings//comments//edit/", + views.edit_meeting_comment, + name="edit_meeting_comment", + ), + path( + "meetings//comments//delete/", + views.delete_meeting_comment, + name="delete_meeting_comment", + ), + path( + "meetings//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//', views.agency_detail, name='agency_detail'), - path('agencies//update/', views.agency_update, name='agency_update'), - path('agencies//delete/', views.agency_delete, name='agency_delete'), - path('agencies//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//", views.agency_detail, name="agency_detail"), + path("agencies//update/", views.agency_update, name="agency_update"), + path("agencies//delete/", views.agency_delete, name="agency_delete"), + path( + "agencies//candidates/", + views.agency_candidates, + name="agency_candidates", + ), # path('agencies//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//create/', views.agency_assignment_create, name='agency_assignment_create'), - path('agency-assignments//', views.agency_assignment_detail, name='agency_assignment_detail'), - path('agency-assignments//update/', views.agency_assignment_update, name='agency_assignment_update'), - path('agency-assignments//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//create/", + views.agency_assignment_create, + name="agency_assignment_create", + ), + path( + "agency-assignments//", + views.agency_assignment_detail, + name="agency_assignment_detail", + ), + path( + "agency-assignments//update/", + views.agency_assignment_update, + name="agency_assignment_update", + ), + path( + "agency-assignments//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//', views.agency_access_link_detail, name='agency_access_link_detail'), - path('agency-access-links//deactivate/', views.agency_access_link_deactivate, name='agency_access_link_deactivate'), - path('agency-access-links//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//", + views.agency_access_link_detail, + name="agency_access_link_detail", + ), + path( + "agency-access-links//deactivate/", + views.agency_access_link_deactivate, + name="agency_access_link_deactivate", + ), + path( + "agency-access-links//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//reply/', views.admin_message_reply, name='admin_message_reply'), # path('admin/messages//mark-read/', views.admin_mark_message_read, name='admin_mark_message_read'), # path('admin/messages//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//', views.agency_portal_assignment_detail, name='agency_portal_assignment_detail'), - path('portal/assignment//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//", + views.agency_portal_assignment_detail, + name="agency_portal_assignment_detail", + ), + path( + "portal/assignment//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//edit/', views.agency_portal_edit_candidate, name='agency_portal_edit_candidate'), - path('portal/candidates//delete/', views.agency_portal_delete_candidate, name='agency_portal_delete_candidate'), - + path( + "portal/candidates//edit/", + views.agency_portal_edit_candidate, + name="agency_portal_edit_candidate", + ), + path( + "portal/candidates//delete/", + views.agency_portal_delete_candidate, + name="agency_portal_delete_candidate", + ), # API URLs for messaging (removed) # path('api/agency/messages//', views.api_agency_message_detail, name='api_agency_message_detail'), # path('api/agency/messages//mark-read/', views.api_agency_mark_message_read, name='api_agency_mark_message_read'), - # API URLs for candidate management - path('api/candidate//', views.api_candidate_detail, name='api_candidate_detail'), - + path( + "api/candidate//", + 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//', views.notification_detail, name='notification_detail'), @@ -222,27 +547,63 @@ urlpatterns = [ # path('notifications//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//', views_frontend.ParticipantsDetailView.as_view(), name='participants_detail'), - path('participants//update/', views_frontend.ParticipantsUpdateView.as_view(), name='participants_update'), - path('participants//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//", + views_frontend.ParticipantsDetailView.as_view(), + name="participants_detail", + ), + path( + "participants//update/", + views_frontend.ParticipantsUpdateView.as_view(), + name="participants_update", + ), + path( + "participants//delete/", + views_frontend.ParticipantsDeleteView.as_view(), + name="participants_delete", + ), # Email composition URLs + path( + "jobs//candidates//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//", views.message_detail, name="message_detail"), + path("messages//reply/", views.message_reply, name="message_reply"), + path("messages//mark-read/", views.message_mark_read, name="message_mark_read"), + path("messages//mark-unread/", views.message_mark_unread, name="message_mark_unread"), + path("messages//delete/", views.message_delete, name="message_delete"), + path("api/unread-count/", views.api_unread_count, name="api_unread_count"), + + # Documents + path("documents/upload//", views.document_upload, name="document_upload"), + path("documents//delete/", views.document_delete, name="document_delete"), + path("documents//download/", views.document_download, name="document_download"), path('jobs//candidates/compose_email/', views.compose_candidate_email, name='compose_candidate_email'), path('interview/partcipants//',views.create_interview_participants,name='create_interview_participants'), path('interview/email//',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//', views.ScheduledInterviewDetailView.as_view(), name='scheduled_interview_detail'), # path('interviews//update/', views.ScheduledInterviewUpdateView.as_view(), name='update_scheduled_interview'), # path('interviews//delete/', views.ScheduledInterviewDeleteView.as_view(), name='delete_scheduled_interview'), - + ] diff --git a/recruitment/views.py b/recruitment/views.py index c77b52e..c4e1841 100644 --- a/recruitment/views.py +++ b/recruitment/views.py @@ -4,58 +4,71 @@ import zipfile from django.core.paginator import Paginator from django.utils.translation import gettext as _ -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model, authenticate, login, logout from django.contrib.auth.decorators import login_required from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.mixins import LoginRequiredMixin - -from rich import print - +from .decorators import ( + agency_user_required, + candidate_user_required, + staff_user_required, + staff_or_agency_required, + staff_or_candidate_required, + AgencyRequiredMixin, + CandidateRequiredMixin, + StaffRequiredMixin, + StaffOrAgencyRequiredMixin, + StaffOrCandidateRequiredMixin +) +from .forms import StaffUserCreationForm,ToggleAccountForm, JobPostingStatusForm,LinkedPostContentForm,CandidateEmailForm,InterviewForm,ProfileImageUploadForm,ParticipantsSelectForm from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_http_methods from django.http import HttpResponse, JsonResponse -from datetime import datetime,time,timedelta +from datetime import datetime, time, timedelta from django.views import View from django.urls import reverse from django.conf import settings from django.utils import timezone -from django.db.models import FloatField,CharField, DurationField -from django.db.models import F, IntegerField, Count, Avg, Sum, Q, ExpressionWrapper, fields +from django.db.models import FloatField, CharField, DurationField +from django.db.models import ( + F, + IntegerField, + Count, + Avg, + Sum, + Q, + ExpressionWrapper, + fields, +) from django.db.models.functions import Cast, Coalesce, TruncDate from django.db.models.fields.json import KeyTextTransform from django.db.models.expressions import ExpressionWrapper from django.urls import reverse_lazy from django.db.models import Count, Avg, F,Q from .forms import ( - CandidateExamDateForm, - InterviewForm, ZoomMeetingForm, + CandidateExamDateForm, JobPostingForm, - FormTemplateForm, - InterviewScheduleForm,JobPostingStatusForm, - BreakTimeFormSet, JobPostingImageForm, - ProfileImageUploadForm, - StaffUserCreationForm, MeetingCommentForm, - ToggleAccountForm, - HiringAgencyForm, - AgencyCandidateSubmissionForm, - AgencyLoginForm, - AgencyAccessLinkForm, - AgencyJobAssignmentForm, - LinkedPostContentForm, - CandidateEmailForm, + InterviewScheduleForm, + FormTemplateForm, SourceForm, - InterviewEmailForm, - + HiringAgencyForm, + AgencyJobAssignmentForm, + AgencyAccessLinkForm, + AgencyApplicationSubmissionForm, + AgencyLoginForm, + PortalLoginForm, + MessageForm, + PersonForm ) from easyaudit.models import CRUDEvent, LoginEvent, RequestEvent from rest_framework import viewsets from django.contrib import messages from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from .linkedin_service import LinkedInService -from .serializers import JobPostingSerializer, CandidateSerializer +from .serializers import JobPostingSerializer, ApplicationSerializer from django.shortcuts import get_object_or_404, render, redirect from django.views.generic import CreateView, UpdateView, DetailView, ListView,DeleteView from .utils import ( @@ -79,15 +92,20 @@ from .models import ( InterviewSchedule, BreakTime, ZoomMeeting, - Candidate, + Application, + Person, JobPosting, ScheduledInterview, JobPostingImage, - Profile,MeetingComment,HiringAgency, + Profile, + MeetingComment, + HiringAgency, AgencyJobAssignment, AgencyAccessLink, Notification, - Source + Source, + Message, + Document, ) import logging from datastar_py.django import ( @@ -100,20 +118,70 @@ from django_q.tasks import async_task from django.db.models import Prefetch from django.db.models import Q, Count, Avg from django.db.models import FloatField +from django.urls import reverse_lazy logger = logging.getLogger(__name__) +User = get_user_model() + +class PersonListView(StaffRequiredMixin, ListView): + model = Person + template_name = "people/person_list.html" + context_object_name = "people_list" + + +class PersonCreateView(CreateView): + model = Person + template_name = "people/create_person.html" + form_class = PersonForm + # success_url = reverse_lazy("person_list") + + def form_valid(self, form): + if 'HX-Request' in self.request.headers: + instance = form.save() + view = self.request.POST.get("view") + if view == "portal": + slug = self.request.POST.get("agency") + if slug: + agency = HiringAgency.objects.get(slug=slug) + print(agency) + instance.agency = agency + instance.save() + return redirect("agency_portal_persons_list") + if view == "job": + return redirect("candidate_create") + return super().form_valid(form) + + +class PersonDetailView(DetailView): + model = Person + template_name = "people/person_detail.html" + context_object_name = "person" + + +class PersonUpdateView(StaffRequiredMixin, UpdateView): + model = Person + template_name = "people/update_person.html" + form_class = PersonForm + success_url = reverse_lazy("person_list") + + +class PersonDeleteView(StaffRequiredMixin, DeleteView): + model = Person + template_name = "people/delete_person.html" + success_url = reverse_lazy("person_list") + class JobPostingViewSet(viewsets.ModelViewSet): queryset = JobPosting.objects.all() serializer_class = JobPostingSerializer class CandidateViewSet(viewsets.ModelViewSet): - queryset = Candidate.objects.all() - serializer_class = CandidateSerializer + queryset = Application.objects.all() + serializer_class = ApplicationSerializer -class ZoomMeetingCreateView(LoginRequiredMixin, CreateView): +class ZoomMeetingCreateView(StaffRequiredMixin, CreateView): model = ZoomMeeting template_name = "meetings/create_meeting.html" form_class = ZoomMeetingForm @@ -125,7 +193,9 @@ class ZoomMeetingCreateView(LoginRequiredMixin, CreateView): topic = instance.topic if instance.start_time < timezone.now(): messages.error(self.request, "Start time must be in the future.") - return redirect(reverse("create_meeting",kwargs={"slug": instance.slug})) + return redirect( + reverse("create_meeting", kwargs={"slug": instance.slug}) + ) start_time = instance.start_time duration = instance.duration @@ -143,13 +213,15 @@ class ZoomMeetingCreateView(LoginRequiredMixin, CreateView): return redirect(reverse("list_meetings")) else: messages.error(self.request, result["message"]) - return redirect(reverse("create_meeting",kwargs={"slug": instance.slug})) + return redirect( + reverse("create_meeting", kwargs={"slug": instance.slug}) + ) except Exception as e: messages.error(self.request, f"Error creating meeting: {e}") - return redirect(reverse("create_meeting",kwargs={"slug": instance.slug})) + return redirect(reverse("create_meeting", kwargs={"slug": instance.slug})) -class ZoomMeetingListView(LoginRequiredMixin, ListView): +class ZoomMeetingListView(StaffRequiredMixin, ListView): model = ZoomMeeting template_name = "meetings/list_meetings.html" context_object_name = "meetings" @@ -162,14 +234,16 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView): queryset = queryset.prefetch_related( Prefetch( - 'interview', # related_name from ZoomMeeting to ScheduledInterview - queryset=ScheduledInterview.objects.select_related('candidate', 'job'), - to_attr='interview_details' # Changed to not start with underscore + "interview", # related_name from ZoomMeeting to ScheduledInterview + queryset=ScheduledInterview.objects.select_related("application", "job"), + to_attr="interview_details", # Changed to not start with underscore ) ) # Handle search by topic or meeting_id - search_query = self.request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency + search_query = self.request.GET.get( + "q", "" + ) # Renamed from 'search' to 'q' for consistency if search_query: queryset = queryset.filter( Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query) @@ -185,8 +259,8 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView): if candidate_name: # Filter based on the name of the candidate associated with the meeting's interview queryset = queryset.filter( - Q(interview__candidate__first_name__icontains=candidate_name) | - Q(interview__candidate__last_name__icontains=candidate_name) + Q(interview__application__first_name__icontains=candidate_name) + | Q(interview__application__last_name__icontains=candidate_name) ) return queryset @@ -197,7 +271,7 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView): context["status_filter"] = self.request.GET.get("status", "") context["candidate_name_filter"] = self.request.GET.get("candidate_name", "") return context - + # @login_required # def InterviewListView(request): # # interview_type=request.GET.get('interview_type','Remote') @@ -208,7 +282,41 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView): # 'meetings':meetings, # }) - + + # search_query = request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency + # if search_query: + # interviews = interviews.filter( + # Q(topic__icontains=search_query) | Q(meeting_id__icontains=search_query) + # ) + + # # Handle filter by status + # status_filter = request.GET.get("status", "") + # if status_filter: + # queryset = queryset.filter(status=status_filter) + + # # Handle search by candidate name + # candidate_name = request.GET.get("candidate_name", "") + # if candidate_name: + # # Filter based on the name of the candidate associated with the meeting's interview + # queryset = queryset.filter( + # Q(interview__candidate__first_name__icontains=candidate_name) | + # Q(interview__candidate__last_name__icontains=candidate_name) + # ) + + + + +# @login_required +# def InterviewListView(request): +# # interview_type=request.GET.get('interview_type','Remote') +# # print(interview_type) +# interview_type='Onsite' +# meetings=ScheduledInterview.objects.filter(schedule__interview_type=interview_type) +# return render(request, "meetings/list_meetings.html",{ +# 'meetings':meetings, +# }) + + # search_query = request.GET.get("q", "") # Renamed from 'search' to 'q' for consistency # if search_query: # interviews = interviews.filter( @@ -233,20 +341,21 @@ class ZoomMeetingListView(LoginRequiredMixin, ListView): -class ZoomMeetingDetailsView(LoginRequiredMixin, DetailView): +class ZoomMeetingDetailsView(StaffRequiredMixin, DetailView): model = ZoomMeeting template_name = "meetings/meeting_details.html" context_object_name = "meeting" + def get_context_data(self, **kwargs): context=super().get_context_data(**kwargs) - meeting = self.object + meeting = self.object try: interview=meeting.interview except Exception as e: print(e) candidate = interview.candidate job=meeting.get_job - + # Assuming interview.participants and interview.system_users hold the people: participants = list(interview.participants.all()) + list(interview.system_users.all()) external_participants=list(interview.participants.all()) @@ -263,10 +372,8 @@ class ZoomMeetingDetailsView(LoginRequiredMixin, DetailView): ) context['total_participants']=total_participants return context - - -class ZoomMeetingUpdateView(LoginRequiredMixin, UpdateView): +class ZoomMeetingUpdateView(StaffRequiredMixin, UpdateView): model = ZoomMeeting form_class = ZoomMeetingForm context_object_name = "meeting" @@ -311,20 +418,33 @@ class ZoomMeetingUpdateView(LoginRequiredMixin, UpdateView): def ZoomMeetingDeleteView(request, slug): meeting = get_object_or_404(ZoomMeeting, slug=slug) if "HX-Request" in request.headers: - return render(request, "meetings/delete_meeting_form.html", {"meeting": meeting,"delete_url": reverse("delete_meeting", kwargs={"slug": meeting.slug})}) + return render( + request, + "meetings/delete_meeting_form.html", + { + "meeting": meeting, + "delete_url": reverse("delete_meeting", kwargs={"slug": meeting.slug}), + }, + ) if request.method == "POST": try: result = delete_zoom_meeting(meeting.meeting_id) - if result["status"] == "success" or "Meeting does not exist" in result["details"]["message"]: + if ( + result["status"] == "success" + or "Meeting does not exist" in result["details"]["message"] + ): meeting.delete() messages.success(request, "Meeting deleted successfully.") else: - messages.error(request, f"{result["message"]} , {result['details']["message"]}") + messages.error( + request, f"{result['message']} , {result['details']['message']}" + ) return redirect(reverse("list_meetings")) except Exception as e: messages.error(request, str(e)) return redirect(reverse("list_meetings")) + # Job Posting # def job_list(request): # """Display the list of job postings order by creation date descending""" @@ -348,21 +468,24 @@ def ZoomMeetingDeleteView(request, slug): @login_required +@staff_user_required def create_job(request): """Create a new job posting""" if request.method == "POST": - form = JobPostingForm( - request.POST - ) + form = JobPostingForm(request.POST) # to check user is authenticated or not if form.is_valid(): try: job = form.save(commit=False) job.save() - job_apply_url_relative=reverse('application_detail',kwargs={'slug':job.slug}) - job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative) - job.application_url=job_apply_url_absolute + job_apply_url_relative = reverse( + "application_detail", kwargs={"slug": job.slug} + ) + job_apply_url_absolute = request.build_absolute_uri( + job_apply_url_relative + ) + job.application_url = job_apply_url_absolute # FormTemplate.objects.create(job=job, is_active=False, name=job.title,created_by=request.user) job.save() messages.success(request, f'Job "{job.title}" created successfully!') @@ -378,14 +501,12 @@ def create_job(request): @login_required +@staff_user_required def edit_job(request, slug): """Edit an existing job posting""" job = get_object_or_404(JobPosting, slug=slug) if request.method == "POST": - form = JobPostingForm( - request.POST, - instance=job - ) + form = JobPostingForm(request.POST, instance=job) if form.is_valid(): try: form.save() @@ -398,37 +519,35 @@ def edit_job(request, slug): messages.error(request, "Please correct the errors below.") else: job = get_object_or_404(JobPosting, slug=slug) - form = JobPostingForm( - instance=job - ) + form = JobPostingForm(instance=job) return render(request, "jobs/edit_job.html", {"form": form, "job": job}) -SCORE_PATH = 'ai_analysis_data__analysis_data__match_score' -HIGH_POTENTIAL_THRESHOLD=75 +SCORE_PATH = "ai_analysis_data__analysis_data__match_score" +HIGH_POTENTIAL_THRESHOLD = 75 from django.contrib.sites.shortcuts import get_current_site -@login_required + + +@staff_user_required def job_detail(request, slug): """View details of a specific job""" job = get_object_or_404(JobPosting, slug=slug) - current_site=get_current_site(request) - print(current_site) - # Get all candidates for this job, ordered by most recent - applicants = job.candidates.all().order_by("-created_at") + # Get all applications for this job, ordered by most recent + applicants = job.applications.all().order_by("-created_at") - # Count candidates by stage for summary statistics + # Count applications by stage for summary statistics total_applicant = applicants.count() applied_count = applicants.filter(stage="Applied").count() - exam_count=applicants.filter(stage="Exam").count + exam_count = applicants.filter(stage="Exam").count() interview_count = applicants.filter(stage="Interview").count() offer_count = applicants.filter(stage="Offer").count() status_form = JobPostingStatusForm(instance=job) - linkedin_content_form=LinkedPostContentForm(instance=job) + linkedin_content_form = LinkedPostContentForm(instance=job) try: # If the related object exists, use its instance data image_upload_form = JobPostingImageForm(instance=job.post_images) @@ -436,35 +555,32 @@ def job_detail(request, slug): # If the related object does NOT exist, create a blank form image_upload_form = JobPostingImageForm() - # 2. Check for POST request (Status Update Submission) - if request.method == 'POST': - + if request.method == "POST": status_form = JobPostingStatusForm(request.POST, instance=job) if status_form.is_valid(): - job_status=status_form.cleaned_data['status'] - form_template=job.form_template - if job_status=='ACTIVE': - form_template.is_active=True - form_template.save(update_fields=['is_active']) + job_status = status_form.cleaned_data["status"] + form_template = job.form_template + if job_status == "ACTIVE": + form_template.is_active = True + form_template.save(update_fields=["is_active"]) else: - form_template.is_active=False - form_template.save(update_fields=['is_active']) + form_template.is_active = False + form_template.save(update_fields=["is_active"]) status_form.save() # Add a success message - messages.success(request, f"Status for '{job.title}' updated to '{job.get_status_display()}' successfully!") + messages.success( + request, + f"Status for '{job.title}' updated to '{job.get_status_display()}' successfully!", + ) - - return redirect('job_detail', slug=slug) + return redirect("job_detail", slug=slug) else: - - messages.error(request, "Failed to update status due to validation errors.") - # --- 2. Quality Metrics (JSON Aggregation) --- # Filter for candidates who have been scored and annotate with a sortable score @@ -478,88 +594,98 @@ def job_detail(request, slug): # # Cast the extracted text score to a FloatField for numerical operations # sortable_score=Cast('score_as_text', output_field=FloatField()) # ) - candidates_with_score = applicants.filter( - is_resume_parsed=True - ).annotate( - annotated_match_score=Coalesce( - Cast(SCORE_PATH, output_field=IntegerField()), - 0 - ) + candidates_with_score = applicants.filter(is_resume_parsed=True).annotate( + annotated_match_score=Coalesce(Cast(SCORE_PATH, output_field=IntegerField()), 0) ) - total_candidates=applicants.count() - avg_match_score_result = candidates_with_score.aggregate(avg_score=Avg('annotated_match_score'))['avg_score'] + total_candidates = applicants.count() + avg_match_score_result = candidates_with_score.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.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 - + high_potential_count = candidates_with_score.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 + ) # --- 3. Time Metrics (Duration Aggregation) --- # Metric: Average Time from Applied to Interview (T2I) - t2i_candidates = applicants.filter( - interview_date__isnull=False - ).annotate( + t2i_candidates = applicants.filter(interview_date__isnull=False).annotate( time_to_interview=ExpressionWrapper( - F('interview_date') - F('created_at'), - output_field=DurationField() + F("interview_date") - F("created_at"), output_field=DurationField() ) ) - avg_t2i_duration = t2i_candidates.aggregate( - avg_t2i=Avg('time_to_interview') - )['avg_t2i'] + avg_t2i_duration = t2i_candidates.aggregate(avg_t2i=Avg("time_to_interview"))[ + "avg_t2i" + ] # Convert timedelta to days - avg_t2i_days = round(avg_t2i_duration.total_seconds() / (60*60*24), 1) if avg_t2i_duration else 0 + avg_t2i_days = ( + round(avg_t2i_duration.total_seconds() / (60 * 60 * 24), 1) + if avg_t2i_duration + else 0 + ) # Metric: Average Time in Exam Stage t_in_exam_candidates = applicants.filter( exam_date__isnull=False, interview_date__isnull=False ).annotate( time_in_exam=ExpressionWrapper( - F('interview_date') - F('exam_date'), - output_field=DurationField() + F("interview_date") - F("exam_date"), output_field=DurationField() ) ) avg_t_in_exam_duration = t_in_exam_candidates.aggregate( - avg_t_in_exam=Avg('time_in_exam') - )['avg_t_in_exam'] + avg_t_in_exam=Avg("time_in_exam") + )["avg_t_in_exam"] # Convert timedelta to days - avg_t_in_exam_days = round(avg_t_in_exam_duration.total_seconds() / (60*60*24), 1) if avg_t_in_exam_duration else 0 + avg_t_in_exam_days = ( + round(avg_t_in_exam_duration.total_seconds() / (60 * 60 * 24), 1) + if avg_t_in_exam_duration + else 0 + ) - category_data = applicants.filter( - ai_analysis_data__analysis_data__category__isnull=False - ).values('ai_analysis_data__analysis_data__category').annotate( - candidate_count=Count('id'), - category=Cast('ai_analysis_data__analysis_data__category',output_field=CharField()) - ).order_by('ai_analysis_data__analysis_data__category') + category_data = ( + applicants.filter(ai_analysis_data__analysis_data__category__isnull=False) + .values("ai_analysis_data__analysis_data__category") + .annotate( + candidate_count=Count("id"), + category=Cast( + "ai_analysis_data__analysis_data__category", output_field=CharField() + ), + ) + .order_by("ai_analysis_data__analysis_data__category") + ) # Prepare data for Chart.js print(category_data) - categories = [item['category'] for item in category_data] - candidate_counts = [item['candidate_count'] for item in category_data] + categories = [item["category"] for item in category_data] + candidate_counts = [item["candidate_count"] for item in category_data] # avg_scores = [round(item['avg_match_score'], 2) if item['avg_match_score'] is not None else 0 for item in category_data] - context = { "job": job, "applicants": applicants, - "total_applicants": total_applicant, # This was total_candidates in the prompt, using total_applicant for consistency + "total_applicants": total_applicant, # This was total_candidates in the prompt, using total_applicant for consistency "applied_count": applied_count, - 'exam_count':exam_count, + "exam_count": exam_count, "interview_count": interview_count, "offer_count": offer_count, - 'status_form':status_form, - 'image_upload_form':image_upload_form, - 'categories': categories, - 'candidate_counts': candidate_counts, + "status_form": status_form, + "image_upload_form": image_upload_form, + "categories": categories, + "candidate_counts": candidate_counts, # 'avg_scores': avg_scores, # New statistics - 'avg_match_score': avg_match_score, - 'high_potential_count': high_potential_count, - 'high_potential_ratio': high_potential_ratio, - 'avg_t2i_days': avg_t2i_days, - 'avg_t_in_exam_days': avg_t_in_exam_days, - 'linkedin_content_form':linkedin_content_form + "avg_match_score": avg_match_score, + "high_potential_count": high_potential_count, + "high_potential_ratio": high_potential_ratio, + "avg_t2i_days": avg_t2i_days, + "avg_t_in_exam_days": avg_t_in_exam_days, + "linkedin_content_form": linkedin_content_form, } return render(request, "jobs/job_detail.html", context) @@ -568,17 +694,17 @@ def job_detail(request, slug): ALLOWED_EXTENSIONS = ('.pdf', '.docx') def job_cvs_download(request,slug): - + job = get_object_or_404(JobPosting,slug=slug) entries=Candidate.objects.filter(job=job) - + # 2. Create an in-memory byte stream (BytesIO) zip_buffer = io.BytesIO() # 3. Create the ZIP archive with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: - + for entry in entries: # Check if the file field has a file if not entry.resume: @@ -587,84 +713,92 @@ def job_cvs_download(request,slug): # Get the file name and check extension (case-insensitive) file_name = entry.resume.name.split('/')[-1] file_name_lower = file_name.lower() - + if file_name_lower.endswith(ALLOWED_EXTENSIONS): try: # Open the file object (rb is read binary) file_obj = entry.resume.open('rb') - + # *** ROBUST METHOD: Read the content and write it to the ZIP *** file_content = file_obj.read() - + # Write the file content directly to the ZIP archive - zf.writestr(file_name, file_content) - + zf.writestr(file_name, file_content) + file_obj.close() - + except Exception as e: # Log the error but continue with the rest of the files print(f"Error processing file {file_name}: {e}") continue - + # 4. Prepare the response zip_buffer.seek(0) - + # 5. Create the HTTP response response = HttpResponse(zip_buffer.read(), content_type='application/zip') - + # Set the header for the browser to download the file response['Content-Disposition'] = 'attachment; filename=f"all_cvs_for_{job.title}.zip"' - + return response @login_required +@staff_user_required def job_image_upload(request, slug): - #only for handling the post request - job=get_object_or_404(JobPosting,slug=slug) + # only for handling the post request + job = get_object_or_404(JobPosting, slug=slug) try: instance = JobPostingImage.objects.get(job=job) except JobPostingImage.DoesNotExist: # If it doesn't exist, create a new instance placeholder instance = None - if request.method == 'POST': + if request.method == "POST": # Pass the existing instance to the form if it exists - image_upload_form = JobPostingImageForm(request.POST, request.FILES, instance=instance) + image_upload_form = JobPostingImageForm( + request.POST, request.FILES, instance=instance + ) if image_upload_form.is_valid(): - # If creating a new one (instance is None), set the job link manually if instance is None: image_instance = image_upload_form.save(commit=False) image_instance.job = job image_instance.save() - messages.success(request, f"Image uploaded successfully for {job.title}.") + messages.success( + request, f"Image uploaded successfully for {job.title}." + ) else: # If updating, the form will update the instance passed to it image_upload_form.save() - messages.success(request, f"Image updated successfully for {job.title}.") + messages.success( + request, f"Image updated successfully for {job.title}." + ) else: - - messages.error(request, "Image upload failed: Please ensure a valid image file was selected.") - return redirect('job_detail', slug=job.slug) - return redirect('job_detail', slug=job.slug) + messages.error( + request, + "Image upload failed: Please ensure a valid image file was selected.", + ) + return redirect("job_detail", slug=job.slug) + return redirect("job_detail", slug=job.slug) @login_required -def edit_linkedin_post_content(request,slug): - - job=get_object_or_404(JobPosting,slug=slug) - linkedin_content_form=LinkedPostContentForm(instance=job) - if request.method=='POST': - linkedin_content_form=LinkedPostContentForm(request.POST,instance=job) +@staff_user_required +def edit_linkedin_post_content(request, slug): + job = get_object_or_404(JobPosting, slug=slug) + linkedin_content_form = LinkedPostContentForm(instance=job) + if request.method == "POST": + linkedin_content_form = LinkedPostContentForm(request.POST, instance=job) if linkedin_content_form.is_valid(): linkedin_content_form.save() - messages.success(request,"Linked post content updated successfully!") - return redirect('job_detail',job.slug) + messages.success(request, "Linked post content updated successfully!") + return redirect("job_detail", job.slug) else: - messages.error(request,"Error update the Linkedin Post content") - return redirect('job_detail',job.slug) + messages.error(request, "Error update the Linkedin Post content") + return redirect("job_detail", job.slug) else: linkedin_content_form=LinkedPostContentForm() @@ -690,27 +824,24 @@ WORKPLACE_TYPES = [ def kaauh_career(request): - active_jobs = JobPosting.objects.select_related( - 'form_template' - ).filter( - status='ACTIVE', - form_template__is_active=True + active_jobs = JobPosting.objects.select_related("form_template").filter( + status="ACTIVE", form_template__is_active=True ) selected_department=request.GET.get('department','') department_type_keys=active_jobs.exclude( department__isnull=True ).exclude(department__exact='' ).values_list( - 'department', + 'department', flat=True ).distinct().order_by('department') - + if selected_department and selected_department in department_type_keys: active_jobs=active_jobs.filter(department=selected_department) selected_workplace_type=request.GET.get('workplace_type','') print(selected_workplace_type) selected_job_type = request.GET.get('employment_type', '') - + job_type_keys = active_jobs.values_list('job_type', flat=True).distinct() workplace_type_keys=active_jobs.values_list('workplace_type',flat=True).distinct() if selected_job_type and selected_job_type in job_type_keys: @@ -721,14 +852,14 @@ def kaauh_career(request): JOBS_PER_PAGE=10 paginator = Paginator(active_jobs, JOBS_PER_PAGE) page_number = request.GET.get('page', 1) - + try: page_obj = paginator.get_page(page_number) except EmptyPage: page_obj = paginator.page(paginator.num_pages) - + total_open_roles=active_jobs.all().count() - + return render(request,'applicant/career.html',{'active_jobs': page_obj.object_list, 'job_type_keys':job_type_keys, @@ -745,9 +876,8 @@ def application_detail(request, slug): return render(request, "applicant/application_detail.html", {"job": job}) -from django_q.tasks import async_task - @login_required +@staff_user_required def post_to_linkedin(request, slug): """Post a job to LinkedIn""" job = get_object_or_404(JobPosting, slug=slug) @@ -756,15 +886,14 @@ def post_to_linkedin(request, slug): return redirect("job_list") if request.method == "POST": - linkedin_access_token=request.session.get("linkedin_access_token") - # Check if user is authenticated with LinkedIn + linkedin_access_token = request.session.get("linkedin_access_token") + # Check if user is authenticated with LinkedIn if not "linkedin_access_token": - messages.error(request, "Please authenticate with LinkedIn first.") - return redirect("linkedin_login") + messages.error(request, "Please authenticate with LinkedIn first.") + return redirect("linkedin_login") try: - # Clear previous LinkedIn data for re-posting - #Prepare the job object for background processing + # Prepare the job object for background processing job.posted_to_linkedin = False job.linkedin_post_id = "" job.linkedin_post_url = "" @@ -776,19 +905,21 @@ def post_to_linkedin(request, slug): # Pass the function path, the job slug, and the token as arguments async_task( - 'recruitment.tasks.linkedin_post_task', - job.slug, - linkedin_access_token + "recruitment.tasks.linkedin_post_task", job.slug, linkedin_access_token ) messages.success( request, - _(f"✅ Job posting process for job with JOB ID: {job.internal_job_id} started! Check the job details page in a moment for the final status.") + _( + f"✅ Job posting process for job with JOB ID: {job.internal_job_id} started! Check the job details page in a moment for the final status." + ), ) except Exception as e: logger.error(f"Error enqueuing LinkedIn post: {e}") - messages.error(request, _("Failed to start the job posting process. Please try again.")) + messages.error( + request, _("Failed to start the job posting process. Please try again.") + ) return redirect("job_detail", slug=job.slug) @@ -832,23 +963,24 @@ def linkedin_callback(request): # applicant views def applicant_job_detail(request, slug): """View job details for applicants""" - job=get_object_or_404(JobPosting,slug=slug,status='ACTIVE') - return render(request,'jobs/applicant_job_detail.html',{'job':job}) + job = get_object_or_404(JobPosting, slug=slug, status="ACTIVE") + return render(request, "jobs/applicant_job_detail.html", {"job": job}) + + +def application_success(request, slug): + job = get_object_or_404(JobPosting, slug=slug) + return render(request, "jobs/application_success.html", {"job": job}) -def application_success(request,slug): - job=get_object_or_404(JobPosting,slug=slug) - return render(request,'jobs/application_success.html',{'job':job}) @ensure_csrf_cookie @login_required +@staff_user_required def form_builder(request, template_slug=None): """Render the form builder interface""" context = {} if template_slug: - template = get_object_or_404( - FormTemplate, slug=template_slug - ) - context['template']=template + template = get_object_or_404(FormTemplate, slug=template_slug) + context["template"] = template context["template_slug"] = template.slug context["template_name"] = template.name return render(request, "forms/form_builder.html", context) @@ -866,18 +998,14 @@ def save_form_template(request): if template_slug: # Update existing template - template = get_object_or_404( - FormTemplate, slug=template_slug - ) + template = get_object_or_404(FormTemplate, slug=template_slug) template.name = template_name template.save() # Clear existing stages and fields template.stages.all().delete() else: # Create new template - template = FormTemplate.objects.create( - name=template_name - ) + template = FormTemplate.objects.create(name=template_name) # Create stages and fields for stage_order, stage_data in enumerate(stages_data): @@ -968,6 +1096,7 @@ def load_form_template(request, template_slug): @login_required +@staff_user_required def form_templates_list(request): """List all form templates for the current user""" query = request.GET.get("q", "") @@ -989,6 +1118,7 @@ def form_templates_list(request): @login_required +@staff_user_required def create_form_template(request): """Create a new form template""" if request.method == "POST": @@ -1008,6 +1138,7 @@ def create_form_template(request): @login_required +@staff_user_required @require_http_methods(["GET"]) def list_form_templates(request): """List all form templates for the current user""" @@ -1018,6 +1149,49 @@ def list_form_templates(request): @login_required +@staff_user_required +def form_submission_details(request, template_id, slug): + """Display detailed view of a specific form submission""" + # Get form template and verify ownership + template = get_object_or_404(FormTemplate, id=template_id) + # Get the specific submission + submission = get_object_or_404(FormSubmission, slug=slug, template=template) + + # Get all stages with their fields + stages = template.stages.prefetch_related("fields").order_by("order") + + # Get all responses for this submission, ordered by field order + responses = submission.responses.select_related("field").order_by("field__order") + + # Group responses by stage + stage_responses = {} + for stage in stages: + stage_responses[stage.id] = { + "stage": stage, + "responses": responses.filter(field__stage=stage), + } + + return render( + request, + "forms/form_submission_details.html", + { + "template": template, + "submission": submission, + "stages": stages, + "responses": responses, + "stage_responses": stage_responses, + }, + ) + # return redirect("application_detail", slug=job.slug) + + # return render( + # request, + # "forms/application_submit_form.html", + # {"template_slug": template_slug, "job_id": job_id}, + # ) + +@login_required +@staff_user_required @require_http_methods(["DELETE"]) def delete_form_template(request, template_id): """Delete a form template""" @@ -1027,25 +1201,30 @@ def delete_form_template(request, template_id): {"success": True, "message": "Form template deleted successfully!"} ) - +@login_required +@staff_user_required def application_submit_form(request, template_slug): """Display the form as a step-by-step wizard""" template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True) job_id = template.job.internal_job_id - job=template.job + job = template.job is_limit_exceeded = job.is_application_limit_reached if is_limit_exceeded: messages.error( - request, - _('Application limit reached: This job is no longer accepting new applications.') + request, + _( + "Application limit reached: This job is no longer accepting new applications." + ), ) - return redirect('application_detail',slug=job.slug) + return redirect("application_detail", slug=job.slug) if job.is_expired: messages.error( - request, - _('Application deadline passed: This job is no longer accepting new applications.') + request, + _( + "Application deadline passed: This job is no longer accepting new applications." + ), ) - return redirect('application_detail',slug=job.slug) + return redirect("application_detail", slug=job.slug) return render( request, @@ -1066,14 +1245,19 @@ def application_submit(request, template_slug): if request.method == "POST": try: with transaction.atomic(): - job_posting = JobPosting.objects.select_for_update().get(form_template=template) + job_posting = JobPosting.objects.select_for_update().get( + form_template=template + ) - current_count = job_posting.candidates.count() + current_count = job_posting.applications.count() if current_count >= job_posting.max_applications: template.is_active = False template.save() return JsonResponse( - {"success": False, "message": "Application limit reached for this job."} + { + "success": False, + "message": "Application limit reached for this job.", + } ) submission = FormSubmission.objects.create(template=template) @@ -1122,22 +1306,24 @@ def application_submit(request, template_slug): submission.applicant_email = email.display_value submission.save() # time=timezone.now() - Candidate.objects.create( + Application.objects.create( first_name=first_name.display_value, last_name=last_name.display_value, email=email.display_value, phone=phone.display_value, address=address.display_value, resume=resume.get_file if resume.is_file else None, - job=job + job=job, ) return JsonResponse( - { - "success": True, - "message": "Form submitted successfully!", - "redirect_url": reverse('application_success',kwargs={'slug':job.slug}), - } - ) + { + "success": True, + "message": "Form submitted successfully!", + "redirect_url": reverse( + "application_success", kwargs={"slug": job.slug} + ), + } + ) # return redirect('application_success',slug=job.slug) except Exception as e: @@ -1161,6 +1347,7 @@ def application_submit(request, template_slug): @login_required +@staff_user_required def form_template_submissions_list(request, slug): """List all submissions for a specific form template""" template = get_object_or_404(FormTemplate, slug=slug) @@ -1182,15 +1369,22 @@ def form_template_submissions_list(request, slug): @login_required +@staff_user_required def form_template_all_submissions(request, template_id): """Display all submissions for a form template in table format""" template = get_object_or_404(FormTemplate, id=template_id) print(template) # Get all submissions for this template - submissions = FormSubmission.objects.filter(template=template).order_by("-submitted_at") + submissions = FormSubmission.objects.filter(template=template).order_by( + "-submitted_at" + ) # Get all fields for this template, ordered by stage and field order - fields = FormField.objects.filter(stage__template=template).select_related('stage').order_by('stage__order', 'order') + fields = ( + FormField.objects.filter(stage__template=template) + .select_related("stage") + .order_by("stage__order", "order") + ) # Pagination paginator = Paginator(submissions, 10) # Show 10 submissions per page @@ -1242,6 +1436,7 @@ def form_submission_details(request, template_id, slug): }, ) + def _handle_get_request(request, slug, job): """ Handles GET requests, setting up forms and restoring candidate selections @@ -1268,8 +1463,9 @@ def _handle_get_request(request, slug, job): # 3. Use the list of IDs to initialize the form if selected_ids: - candidates_to_load = Candidate.objects.filter(pk__in=selected_ids) - form.initial["candidates"] = candidates_to_load + candidates_to_load = Application.objects.filter(pk__in=selected_ids) + print(candidates_to_load) + form.initial["applications"] = candidates_to_load return render( request, @@ -1279,7 +1475,6 @@ def _handle_get_request(request, slug, job): def _handle_preview_submission(request, slug, job): - """ Handles the initial POST request (Preview Schedule). Validates forms, calculates slots, saves data to session, and renders preview. @@ -1290,7 +1485,7 @@ def _handle_preview_submission(request, slug, job): if form.is_valid(): # Get the form data - candidates = form.cleaned_data["candidates"] + applications = form.cleaned_data["applications"] interview_type=form.cleaned_data["interview_type"] start_date = form.cleaned_data["start_date"] end_date = form.cleaned_data["end_date"] @@ -1323,18 +1518,18 @@ def _handle_preview_submission(request, slug, job): start_time=start_time, end_time=end_time, interview_duration=interview_duration, - buffer_time=buffer_time, - break_start_time=break_start_time, - break_end_time=break_end_time + buffer_time=buffer_time or 5, + break_start_time=break_start_time or None, + break_end_time=break_end_time or None, ) # Get available slots (temp_breaks logic moved into get_available_time_slots if needed) available_slots = get_available_time_slots(temp_schedule) - if len(available_slots) < len(candidates): + if len(available_slots) < len(applications): messages.error( request, - f"Not enough available slots. Required: {len(candidates)}, Available: {len(available_slots)}", + f"Not enough available slots. Required: {len(applications)}, Available: {len(available_slots)}", ) return render( request, @@ -1344,10 +1539,10 @@ def _handle_preview_submission(request, slug, job): # Create a preview schedule preview_schedule = [] - for i, candidate in enumerate(candidates): + for i, candidate in enumerate(applications): slot = available_slots[i] preview_schedule.append( - {"candidate": candidate, "date": slot["date"], "time": slot["time"]} + {"applications": applications, "date": slot["date"], "time": slot["time"]} ) # Save the form data to session for later use @@ -1362,7 +1557,7 @@ def _handle_preview_submission(request, slug, job): "buffer_time": buffer_time, "break_start_time": break_start_time.isoformat(), "break_end_time": break_end_time.isoformat(), - "candidate_ids": [c.id for c in candidates], + "candidate_ids": [c.id for c in applications], } request.session[SESSION_DATA_KEY] = schedule_data @@ -1400,7 +1595,6 @@ def _handle_confirm_schedule(request, slug, job): Creates the main schedule record and queues individual interviews asynchronously. """ - SESSION_DATA_KEY = "interview_schedule_data" SESSION_ID_KEY = f"schedule_candidate_ids_{slug}" @@ -1425,7 +1619,6 @@ def _handle_confirm_schedule(request, slug, job): end_time=time.fromisoformat(schedule_data["end_time"]), interview_duration=schedule_data["interview_duration"], buffer_time=schedule_data["buffer_time"], - # Use the simple break times saved in the session # If the value is None (because required=False in form), handle it gracefully break_start_time=schedule_data.get("break_start_time"), @@ -1434,14 +1627,16 @@ def _handle_confirm_schedule(request, slug, job): except Exception as e: # Handle database creation error messages.error(request, f"Error creating schedule: {e}") - if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY] + if SESSION_ID_KEY in request.session: + del request.session[SESSION_ID_KEY] return redirect("schedule_interviews", slug=slug) - # 3. Setup candidates and get slots - candidates = Candidate.objects.filter(id__in=schedule_data["candidate_ids"]) + candidates = Application.objects.filter(id__in=schedule_data["candidate_ids"]) schedule.candidates.set(candidates) - available_slots = get_available_time_slots(schedule) # This should still be synchronous and fast + available_slots = get_available_time_slots( + schedule + ) # This should still be synchronous and fast # 4. Queue scheduled interviews asynchronously (FAST RESPONSE) if schedule.interview_type=='Remote': @@ -1450,34 +1645,34 @@ def _handle_confirm_schedule(request, slug, job): if i < len(available_slots): slot = available_slots[i] - # Dispatch the individual creation task to the background queue - - async_task( - "recruitment.tasks.create_interview_and_meeting", - candidate.pk, - job.pk, - schedule.pk, - slot['date'], - slot['time'], - schedule.interview_duration, - ) - queued_count += 1 + # Dispatch the individual creation task to the background queue + async_task( + "recruitment.tasks.create_interview_and_meeting", + candidate.pk, + job.pk, + schedule.pk, + slot["date"], + slot["time"], + schedule.interview_duration, + ) + queued_count += 1 - # 5. Success and Cleanup (IMMEDIATE RESPONSE) messages.success( request, - f"Schedule successfully created. Queued {queued_count} interviews to be booked asynchronously. Check back in a moment!" + f"Schedule successfully created. Queued {queued_count} interviews to be booked asynchronously. Check back in a moment!", ) # Clear both session data keys upon successful completion - if SESSION_DATA_KEY in request.session: del request.session[SESSION_DATA_KEY] - if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY] + if SESSION_DATA_KEY in request.session: + del request.session[SESSION_DATA_KEY] + if SESSION_ID_KEY in request.session: + del request.session[SESSION_ID_KEY] return redirect("job_detail", slug=slug) else: for i, candidate in enumerate(candidates): if i < len(available_slots): - slot = available_slots[i] + slot = available_slots[i] ScheduledInterview.objects.create( candidate=candidate, job=job, @@ -1486,7 +1681,7 @@ def _handle_confirm_schedule(request, slug, job): interview_date=slot['date'], interview_time= slot['time'] ) - + messages.success( request, f"Onsite schedule Interview Create succesfully" @@ -1497,22 +1692,21 @@ def _handle_confirm_schedule(request, slug, job): if SESSION_ID_KEY in request.session: del request.session[SESSION_ID_KEY] return redirect('schedule_interview_location_form',slug=schedule.slug) - def schedule_interviews_view(request, slug): job = get_object_or_404(JobPosting, slug=slug) if request.method == "POST": - # return _handle_confirm_schedule(request, slug, job) + # return _handle_confirm_schedule(request, slug, job) return _handle_preview_submission(request, slug, job) else: return _handle_get_request(request, slug, job) - + def confirm_schedule_interviews_view(request, slug): job = get_object_or_404(JobPosting, slug=slug) if request.method == "POST": return _handle_confirm_schedule(request, slug, job) -@login_required +@staff_user_required def candidate_screening_view(request, slug): """ Manage candidate tiers and stage transitions @@ -1521,10 +1715,10 @@ def candidate_screening_view(request, slug): candidates = job.screening_candidates # Get filter parameters - min_ai_score_str = request.GET.get('min_ai_score') - min_experience_str = request.GET.get('min_experience') - screening_rating = request.GET.get('screening_rating') - tier1_count_str = request.GET.get('tier1_count') + min_ai_score_str = request.GET.get("min_ai_score") + min_experience_str = request.GET.get("min_experience") + screening_rating = request.GET.get("screening_rating") + tier1_count_str = request.GET.get("tier1_count") try: # Check if the string value exists and is not an empty string before conversion @@ -1551,13 +1745,19 @@ def candidate_screening_view(request, slug): # Apply filters if min_ai_score > 0: - candidates = candidates.filter(ai_analysis_data__analysis_data__match_score__gte=min_ai_score) + candidates = candidates.filter( + ai_analysis_data__analysis_data__match_score__gte=min_ai_score + ) if min_experience > 0: - candidates = candidates.filter(ai_analysis_data__analysis_data__years_of_experience__gte=min_experience) + candidates = candidates.filter( + ai_analysis_data__analysis_data__years_of_experience__gte=min_experience + ) if screening_rating: - candidates = candidates.filter(ai_analysis_data__analysis_data__screening_stage_rating=screening_rating) + candidates = candidates.filter( + ai_analysis_data__analysis_data__screening_stage_rating=screening_rating + ) if tier1_count > 0: candidates = candidates[:tier1_count] @@ -1565,33 +1765,29 @@ def candidate_screening_view(request, slug): context = { "job": job, "candidates": candidates, - 'min_ai_score':min_ai_score, - 'min_experience':min_experience, - 'screening_rating':screening_rating, - 'tier1_count':tier1_count, - "current_stage" : "Applied" + "min_ai_score": min_ai_score, + "min_experience": min_experience, + "screening_rating": screening_rating, + "tier1_count": tier1_count, + "current_stage": "Applied", } return render(request, "recruitment/candidate_screening_view.html", context) -@login_required +@staff_user_required def candidate_exam_view(request, slug): """ Manage candidate tiers and stage transitions """ job = get_object_or_404(JobPosting, slug=slug) - context = { - "job": job, - "candidates": job.exam_candidates, - 'current_stage' : "Exam" - } + context = {"job": job, "candidates": job.exam_candidates, "current_stage": "Exam"} return render(request, "recruitment/candidate_exam_view.html", context) -@login_required +@staff_user_required def update_candidate_exam_status(request, slug): - candidate = get_object_or_404(Candidate, slug=slug) + candidate = get_object_or_404(Application, slug=slug) if request.method == "POST": form = CandidateExamDateForm(request.POST, instance=candidate) if form.is_valid(): @@ -1599,11 +1795,17 @@ def update_candidate_exam_status(request, slug): return redirect("candidate_exam_view", slug=candidate.job.slug) else: form = CandidateExamDateForm(request.POST, instance=candidate) - return render(request, "includes/candidate_exam_status_form.html", {"candidate": candidate,"form": form}) -@login_required -def bulk_update_candidate_exam_status(request,slug): + return render( + request, + "includes/candidate_exam_status_form.html", + {"candidate": candidate, "form": form}, + ) + + +@staff_user_required +def bulk_update_candidate_exam_status(request, slug): job = get_object_or_404(JobPosting, slug=slug) - status = request.headers.get('status') + status = request.headers.get("status") if status: for candidate in get_candidates_from_request(request): try: @@ -1618,71 +1820,140 @@ def bulk_update_candidate_exam_status(request,slug): messages.success(request, f"Updated exam status selected candidates") return redirect("candidate_exam_view", slug=job.slug) + def candidate_criteria_view_htmx(request, pk): - candidate = get_object_or_404(Candidate, pk=pk) - return render(request, "includes/candidate_modal_body.html", {"candidate": candidate}) + candidate = get_object_or_404(Application, pk=pk) + return render( + request, "includes/candidate_modal_body.html", {"candidate": candidate} + ) -@login_required +@staff_user_required def candidate_set_exam_date(request, slug): - candidate = get_object_or_404(Candidate, slug=slug) + candidate = get_object_or_404(Application, slug=slug) candidate.exam_date = timezone.now() candidate.save() - messages.success(request, f"Set exam date for {candidate.name} to {candidate.exam_date}") + messages.success( + request, f"Set exam date for {candidate.name} to {candidate.exam_date}" + ) return redirect("candidate_screening_view", slug=candidate.job.slug) -@login_required + +@staff_user_required def candidate_update_status(request, slug): job = get_object_or_404(JobPosting, slug=slug) - mark_as = request.POST.get('mark_as') - - if mark_as != '----------': + mark_as = request.POST.get("mark_as") + + if mark_as != "----------": candidate_ids = request.POST.getlist("candidate_ids") print(candidate_ids) - if c := Candidate.objects.filter(pk__in = candidate_ids): - - if mark_as=='Exam': - c.update(exam_date=timezone.now(),interview_date=None,offer_date=None,hired_date=None,stage=mark_as,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") - elif mark_as=='Interview': + if c := Application.objects.filter(pk__in=candidate_ids): + if mark_as == "Exam": + c.update( + exam_date=timezone.now(), + interview_date=None, + offer_date=None, + hired_date=None, + stage=mark_as, + applicant_status="Candidate" + if mark_as in ["Exam", "Interview", "Offer"] + else "Applicant", + ) + elif mark_as == "Interview": # interview_date update when scheduling the interview - c.update(stage=mark_as,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") - elif mark_as=='Offer': - c.update(stage=mark_as,offer_date=timezone.now(),hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") - elif mark_as=='Hired': - print('hired') - c.update(stage=mark_as,hired_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") + c.update( + stage=mark_as, + offer_date=None, + hired_date=None, + applicant_status="Candidate" + if mark_as in ["Exam", "Interview", "Offer"] + else "Applicant", + ) + elif mark_as == "Offer": + c.update( + stage=mark_as, + offer_date=timezone.now(), + hired_date=None, + applicant_status="Candidate" + if mark_as in ["Exam", "Interview", "Offer"] + else "Applicant", + ) + elif mark_as == "Hired": + print("hired") + c.update( + stage=mark_as, + hired_date=timezone.now(), + applicant_status="Candidate" + if mark_as in ["Exam", "Interview", "Offer"] + else "Applicant", + ) else: - c.update(stage=mark_as,exam_date=None,interview_date=None,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant") - - + c.update( + stage=mark_as, + exam_date=None, + interview_date=None, + offer_date=None, + hired_date=None, + applicant_status="Candidate" + if mark_as in ["Exam", "Interview", "Offer"] + else "Applicant", + ) messages.success(request, f"Candidates Updated") response = HttpResponse(redirect("candidate_screening_view", slug=job.slug)) response.headers["HX-Refresh"] = "true" return response -@login_required -def candidate_interview_view(request,slug): - job = get_object_or_404(JobPosting,slug=slug) +@staff_user_required +def candidate_interview_view(request, slug): + job = get_object_or_404(JobPosting, slug=slug) + + if request.method == "POST": + form = ParticipantsSelectForm(request.POST, instance=job) + print(form.errors) + + if form.is_valid(): + # Save the main instance (JobPosting) + job_instance = form.save(commit=False) + job_instance.save() + + # MANUALLY set the M2M relationships based on submitted data + job_instance.participants.set(form.cleaned_data["participants"]) + job_instance.users.set(form.cleaned_data["users"]) + + messages.success(request, "Interview participants updated successfully.") + return redirect("candidate_interview_view", slug=job.slug) + + else: + initial_data = { + "participants": job.participants.all(), + "users": job.users.all(), + } + form = ParticipantsSelectForm(instance=job, initial=initial_data) + + else: + form = ParticipantsSelectForm(instance=job) context = { - "job":job, - "candidates":job.interview_candidates, - 'current_stage':'Interview', - + "job": job, + "candidates": job.interview_candidates, + "current_stage": "Interview", + "form": form, + "participants_count": job.participants.count() + job.users.count(), } - return render(request,"recruitment/candidate_interview_view.html",context) + return render(request, "recruitment/candidate_interview_view.html", context) -@login_required -def reschedule_meeting_for_candidate(request,slug,candidate_id,meeting_id): - job = get_object_or_404(JobPosting,slug=slug) - candidate = get_object_or_404(Candidate,pk=candidate_id) - meeting = get_object_or_404(ZoomMeeting,pk=meeting_id) + +@staff_user_required +def reschedule_meeting_for_candidate(request, slug, candidate_id, meeting_id): + job = get_object_or_404(JobPosting, slug=slug) + candidate = get_object_or_404(Application, pk=candidate_id) + meeting = get_object_or_404(ZoomMeeting, pk=meeting_id) form = ZoomMeetingForm(instance=meeting) if request.method == "POST": - form = ZoomMeetingForm(request.POST,instance=meeting) + form = ZoomMeetingForm(request.POST, instance=meeting) if form.is_valid(): instance = form.save(commit=False) updated_data = { @@ -1692,7 +1963,12 @@ def reschedule_meeting_for_candidate(request,slug,candidate_id,meeting_id): } if instance.start_time < timezone.now(): messages.error(request, "Start time must be in the future.") - return redirect("reschedule_meeting_for_candidate",slug=job.slug,candidate_id=candidate_id,meeting_id=meeting_id) + return redirect( + "reschedule_meeting_for_candidate", + slug=job.slug, + candidate_id=candidate_id, + meeting_id=meeting_id, + ) result = update_meeting(instance, updated_data) @@ -1700,46 +1976,62 @@ def reschedule_meeting_for_candidate(request,slug,candidate_id,meeting_id): messages.success(request, result["message"]) else: messages.error(request, result["message"]) - return redirect(reverse("candidate_interview_view", kwargs={"slug": job.slug})) + return redirect( + reverse("candidate_interview_view", kwargs={"slug": job.slug}) + ) - context = {"job":job,"candidate":candidate,"meeting":meeting,"form":form} - return render(request,"meetings/reschedule_meeting.html",context) + context = {"job": job, "candidate": candidate, "meeting": meeting, "form": form} + return render(request, "meetings/reschedule_meeting.html", context) -@login_required -def delete_meeting_for_candidate(request,slug,candidate_pk,meeting_id): - job = get_object_or_404(JobPosting,slug=slug) - candidate = get_object_or_404(Candidate,pk=candidate_pk) - meeting = get_object_or_404(ZoomMeeting,pk=meeting_id) +@staff_user_required +def delete_meeting_for_candidate(request, slug, candidate_pk, meeting_id): + job = get_object_or_404(JobPosting, slug=slug) + candidate = get_object_or_404(Application, pk=candidate_pk) + meeting = get_object_or_404(ZoomMeeting, pk=meeting_id) if request.method == "POST": result = delete_zoom_meeting(meeting.meeting_id) - if result["status"] == "success" or "Meeting does not exist" in result["details"]["message"]: + if ( + result["status"] == "success" + or "Meeting does not exist" in result["details"]["message"] + ): meeting.delete() messages.success(request, "Meeting deleted successfully") else: messages.error(request, result["message"]) return redirect(reverse("candidate_interview_view", kwargs={"slug": job.slug})) - context = {"job":job,"candidate":candidate,"meeting":meeting,'delete_url':reverse("delete_meeting_for_candidate",kwargs={"slug":job.slug,"candidate_pk":candidate_pk,"meeting_id":meeting_id})} - return render(request,"meetings/delete_meeting_form.html",context) + context = { + "job": job, + "candidate": candidate, + "meeting": meeting, + "delete_url": reverse( + "delete_meeting_for_candidate", + kwargs={ + "slug": job.slug, + "candidate_pk": candidate_pk, + "meeting_id": meeting_id, + }, + ), + } + return render(request, "meetings/delete_meeting_form.html", context) -@login_required +@staff_user_required def interview_calendar_view(request, slug): job = get_object_or_404(JobPosting, slug=slug) # Get all scheduled interviews for this job - scheduled_interviews = ScheduledInterview.objects.filter( - job=job - ).select_related('candidate', 'zoom_meeting') + scheduled_interviews = ScheduledInterview.objects.filter(job=job).select_related( + "applicaton", "zoom_meeting" + ) # Convert interviews to calendar events events = [] for interview in scheduled_interviews: # Create start datetime start_datetime = datetime.combine( - interview.interview_date, - interview.interview_time + interview.interview_date, interview.interview_time ) # Calculate end datetime based on interview duration @@ -1747,53 +2039,55 @@ def interview_calendar_view(request, slug): end_datetime = start_datetime + timedelta(minutes=duration) # Determine event color based on status - color = '#00636e' # Default color - if interview.status == 'confirmed': - color = '#00a86b' # Green for confirmed - elif interview.status == 'cancelled': - color = '#e74c3c' # Red for cancelled - elif interview.status == 'completed': - color = '#95a5a6' # Gray for completed + color = "#00636e" # Default color + if interview.status == "confirmed": + color = "#00a86b" # Green for confirmed + elif interview.status == "cancelled": + color = "#e74c3c" # Red for cancelled + elif interview.status == "completed": + color = "#95a5a6" # Gray for completed - events.append({ - 'title': f"Interview: {interview.candidate.name}", - 'start': start_datetime.isoformat(), - 'end': end_datetime.isoformat(), - 'url': f"{request.path}interview/{interview.id}/", - 'color': color, - 'extendedProps': { - 'candidate': interview.candidate.name, - 'email': interview.candidate.email, - 'status': interview.status, - 'meeting_id': interview.zoom_meeting.meeting_id if interview.zoom_meeting else None, - 'join_url': interview.zoom_meeting.join_url if interview.zoom_meeting else None, + events.append( + { + "title": f"Interview: {interview.candidate.name}", + "start": start_datetime.isoformat(), + "end": end_datetime.isoformat(), + "url": f"{request.path}interview/{interview.id}/", + "color": color, + "extendedProps": { + "candidate": interview.candidate.name, + "email": interview.candidate.email, + "status": interview.status, + "meeting_id": interview.zoom_meeting.meeting_id + if interview.zoom_meeting + else None, + "join_url": interview.zoom_meeting.join_url + if interview.zoom_meeting + else None, + }, } - }) + ) context = { - 'job': job, - 'events': events, - 'calendar_color': '#00636e', + "job": job, + "events": events, + "calendar_color": "#00636e", } - return render(request, 'recruitment/interview_calendar.html', context) + return render(request, "recruitment/interview_calendar.html", context) -@login_required +@staff_user_required def interview_detail_view(request, slug, interview_id): job = get_object_or_404(JobPosting, slug=slug) - interview = get_object_or_404( - ScheduledInterview, - id=interview_id, - job=job - ) + interview = get_object_or_404(ScheduledInterview, id=interview_id, job=job) context = { - 'job': job, - 'interview': interview, + "job": job, + "interview": interview, } - return render(request, 'recruitment/interview_detail.html', context) + return render(request, "recruitment/interview_detail.html", context) # Candidate Meeting Scheduling/Rescheduling Views @@ -1804,14 +2098,16 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): Returns JSON response for modal update. """ job = get_object_or_404(JobPosting, slug=job_slug) - candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) + candidate = get_object_or_404(Application, pk=candidate_pk, job=job) topic = f"Interview: {job.title} with {candidate.name}" - start_time_str = request.POST.get('start_time') - duration = int(request.POST.get('duration', 60)) + start_time_str = request.POST.get("start_time") + duration = int(request.POST.get("duration", 60)) if not start_time_str: - return JsonResponse({'success': False, 'error': 'Start time is required.'}, status=400) + return JsonResponse( + {"success": False, "error": "Start time is required."}, status=400 + ) try: # Parse datetime from datetime-local input (YYYY-MM-DDTHH:MM) @@ -1821,12 +2117,17 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): # For simplicity, assuming create_zoom_meeting handles naive datetimes or they are in UTC. # If start_time is expected to be in a specific timezone, convert it here. # e.g., start_time = timezone.make_aware(naive_start_time, timezone.get_current_timezone()) - start_time = naive_start_time # Or timezone.make_aware(naive_start_time) + start_time = naive_start_time # Or timezone.make_aware(naive_start_time) except ValueError: - return JsonResponse({'success': False, 'error': 'Invalid date/time format for start time.'}, status=400) + return JsonResponse( + {"success": False, "error": "Invalid date/time format for start time."}, + status=400, + ) if start_time <= timezone.now(): - return JsonResponse({'success': False, 'error': 'Start time must be in the future.'}, status=400) + return JsonResponse( + {"success": False, "error": "Start time must be in the future."}, status=400 + ) result = create_zoom_meeting(topic=topic, start_time=start_time, duration=duration) @@ -1834,7 +2135,7 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): zoom_meeting_details = result["meeting_details"] zoom_meeting = ZoomMeeting.objects.create( topic=topic, - start_time=start_time, # Store in local timezone + start_time=start_time, # Store in local timezone duration=duration, meeting_id=zoom_meeting_details["meeting_id"], join_url=zoom_meeting_details["join_url"], @@ -1849,24 +2150,26 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): zoom_meeting=zoom_meeting, interview_date=start_time.date(), interview_time=start_time.time(), - status='scheduled' # Or 'confirmed' depending on your workflow + status="scheduled", # Or 'confirmed' depending on your workflow ) messages.success(request, f"Meeting scheduled with {candidate.name}.") # Return updated table row or a success message # For HTMX, you might want to return a fragment of the updated table # For now, returning JSON to indicate success and close modal - return JsonResponse({ - 'success': True, - 'message': 'Meeting scheduled successfully!', - 'join_url': zoom_meeting.join_url, - 'meeting_id': zoom_meeting.meeting_id, - 'candidate_name': candidate.name, - 'interview_datetime': start_time.strftime("%Y-%m-%d %H:%M") - }) + return JsonResponse( + { + "success": True, + "message": "Meeting scheduled successfully!", + "join_url": zoom_meeting.join_url, + "meeting_id": zoom_meeting.meeting_id, + "candidate_name": candidate.name, + "interview_datetime": start_time.strftime("%Y-%m-%d %H:%M"), + } + ) else: messages.error(request, result["message"]) - return JsonResponse({'success': False, 'error': result["message"]}, status=400) + return JsonResponse({"success": False, "error": result["message"]}, status=400) def schedule_candidate_meeting(request, job_slug, candidate_pk): @@ -1875,17 +2178,20 @@ def schedule_candidate_meeting(request, job_slug, candidate_pk): POST: Handled by api_schedule_candidate_meeting. """ job = get_object_or_404(JobPosting, slug=job_slug) - candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) + candidate = get_object_or_404(Application, pk=candidate_pk, job=job) if request.method == "POST": return api_schedule_candidate_meeting(request, job_slug, candidate_pk) # GET request - render the form snippet for HTMX context = { - 'job': job, - 'candidate': candidate, - 'action_url': reverse('api_schedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk}), - 'scheduled_interview': None, # Explicitly None for schedule + "job": job, + "candidate": candidate, + "action_url": reverse( + "api_schedule_candidate_meeting", + kwargs={"job_slug": job_slug, "candidate_pk": candidate_pk}, + ), + "scheduled_interview": None, # Explicitly None for schedule } # Render just the form part, or the whole modal body content return render(request, "includes/meeting_form.html", context) @@ -1897,34 +2203,44 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): Handles GET to render form and POST to process scheduling. """ job = get_object_or_404(JobPosting, slug=job_slug) - candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) + candidate = get_object_or_404(Application, pk=candidate_pk, job=job) if request.method == "GET": # This GET is for HTMX to fetch the form context = { - 'job': job, - 'candidate': candidate, - 'action_url': reverse('api_schedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk}), - 'scheduled_interview': None, + "job": job, + "candidate": candidate, + "action_url": reverse( + "api_schedule_candidate_meeting", + kwargs={"job_slug": job_slug, "candidate_pk": candidate_pk}, + ), + "scheduled_interview": None, } return render(request, "includes/meeting_form.html", context) # POST logic (remains the same) topic = f"Interview: {job.title} with {candidate.name}" - start_time_str = request.POST.get('start_time') - duration = int(request.POST.get('duration', 60)) + start_time_str = request.POST.get("start_time") + duration = int(request.POST.get("duration", 60)) if not start_time_str: - return JsonResponse({'success': False, 'error': 'Start time is required.'}, status=400) + return JsonResponse( + {"success": False, "error": "Start time is required."}, status=400 + ) try: naive_start_time = datetime.fromisoformat(start_time_str) start_time = naive_start_time except ValueError: - return JsonResponse({'success': False, 'error': 'Invalid date/time format for start time.'}, status=400) + return JsonResponse( + {"success": False, "error": "Invalid date/time format for start time."}, + status=400, + ) if start_time <= timezone.now(): - return JsonResponse({'success': False, 'error': 'Start time must be in the future.'}, status=400) + return JsonResponse( + {"success": False, "error": "Start time must be in the future."}, status=400 + ) result = create_zoom_meeting(topic=topic, start_time=start_time, duration=duration) @@ -1947,20 +2263,22 @@ def api_schedule_candidate_meeting(request, job_slug, candidate_pk): zoom_meeting=zoom_meeting, interview_date=start_time.date(), interview_time=start_time.time(), - status='scheduled' + status="scheduled", ) messages.success(request, f"Meeting scheduled with {candidate.name}.") - return JsonResponse({ - 'success': True, - 'message': 'Meeting scheduled successfully!', - 'join_url': zoom_meeting.join_url, - 'meeting_id': zoom_meeting.meeting_id, - 'candidate_name': candidate.name, - 'interview_datetime': start_time.strftime("%Y-%m-%d %H:%M") - }) + return JsonResponse( + { + "success": True, + "message": "Meeting scheduled successfully!", + "join_url": zoom_meeting.join_url, + "meeting_id": zoom_meeting.meeting_id, + "candidate_name": candidate.name, + "interview_datetime": start_time.strftime("%Y-%m-%d %H:%M"), + } + ) else: messages.error(request, result["message"]) - return JsonResponse({'success': False, 'error': result["message"]}, status=400) + return JsonResponse({"success": False, "error": result["message"]}, status=400) @require_http_methods(["GET", "POST"]) @@ -1970,44 +2288,58 @@ def api_reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_ """ job = get_object_or_404(JobPosting, slug=job_slug) scheduled_interview = get_object_or_404( - ScheduledInterview.objects.select_related('zoom_meeting'), + ScheduledInterview.objects.select_related("zoom_meeting"), pk=interview_pk, - candidate__pk=candidate_pk, - job=job + application__pk=candidate_pk, + job=job, ) zoom_meeting = scheduled_interview.zoom_meeting if request.method == "GET": # This GET is for HTMX to fetch the form initial_data = { - 'topic': zoom_meeting.topic, - 'start_time': zoom_meeting.start_time.strftime('%Y-%m-%dT%H:%M'), - 'duration': zoom_meeting.duration, + "topic": zoom_meeting.topic, + "start_time": zoom_meeting.start_time.strftime("%Y-%m-%dT%H:%M"), + "duration": zoom_meeting.duration, } context = { - 'job': job, - 'candidate': scheduled_interview.candidate, - 'scheduled_interview': scheduled_interview, # Pass for conditional logic in template - 'initial_data': initial_data, - 'action_url': reverse('api_reschedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk, 'interview_pk': interview_pk}) + "job": job, + "candidate": scheduled_interview.application, + "scheduled_interview": scheduled_interview, # Pass for conditional logic in template + "initial_data": initial_data, + "action_url": reverse( + "api_reschedule_candidate_meeting", + kwargs={ + "job_slug": job_slug, + "candidate_pk": candidate_pk, + "interview_pk": interview_pk, + }, + ), } return render(request, "includes/meeting_form.html", context) # POST logic (remains the same) - new_start_time_str = request.POST.get('start_time') - new_duration = int(request.POST.get('duration', zoom_meeting.duration)) + new_start_time_str = request.POST.get("start_time") + new_duration = int(request.POST.get("duration", zoom_meeting.duration)) if not new_start_time_str: - return JsonResponse({'success': False, 'error': 'New start time is required.'}, status=400) + return JsonResponse( + {"success": False, "error": "New start time is required."}, status=400 + ) try: naive_new_start_time = datetime.fromisoformat(new_start_time_str) new_start_time = naive_new_start_time except ValueError: - return JsonResponse({'success': False, 'error': 'Invalid date/time format for new start time.'}, status=400) + return JsonResponse( + {"success": False, "error": "Invalid date/time format for new start time."}, + status=400, + ) if new_start_time <= timezone.now(): - return JsonResponse({'success': False, 'error': 'Start time must be in the future.'}, status=400) + return JsonResponse( + {"success": False, "error": "Start time must be in the future."}, status=400 + ) updated_data = { "topic": f"Interview: {job.title} with {scheduled_interview.candidate.name}", @@ -2024,36 +2356,52 @@ def api_reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_ zoom_meeting.topic = updated_zoom_details.get("topic", zoom_meeting.topic) zoom_meeting.start_time = new_start_time zoom_meeting.duration = new_duration - zoom_meeting.join_url = updated_zoom_details.get("join_url", zoom_meeting.join_url) - zoom_meeting.password = updated_zoom_details.get("password", zoom_meeting.password) - zoom_meeting.status = updated_zoom_details.get("status", zoom_meeting.status) + zoom_meeting.join_url = updated_zoom_details.get( + "join_url", zoom_meeting.join_url + ) + zoom_meeting.password = updated_zoom_details.get( + "password", zoom_meeting.password + ) + zoom_meeting.status = updated_zoom_details.get( + "status", zoom_meeting.status + ) zoom_meeting.zoom_gateway_response = updated_zoom_details zoom_meeting.save() scheduled_interview.interview_date = new_start_time.date() scheduled_interview.interview_time = new_start_time.time() - scheduled_interview.status = 'rescheduled' + scheduled_interview.status = "rescheduled" scheduled_interview.save() - messages.success(request, f"Meeting for {scheduled_interview.candidate.name} rescheduled.") + messages.success( + request, + f"Meeting for {scheduled_interview.candidate.name} rescheduled.", + ) else: - logger.warning(f"Zoom meeting {zoom_meeting.meeting_id} updated, but failed to fetch latest details.") + logger.warning( + f"Zoom meeting {zoom_meeting.meeting_id} updated, but failed to fetch latest details." + ) zoom_meeting.start_time = new_start_time zoom_meeting.duration = new_duration zoom_meeting.save() scheduled_interview.interview_date = new_start_time.date() scheduled_interview.interview_time = new_start_time.time() scheduled_interview.save() - messages.success(request, f"Meeting for {scheduled_interview.candidate.name} rescheduled. (Note: Could not refresh all details from Zoom.)") + messages.success( + request, + f"Meeting for {scheduled_interview.candidate.name} rescheduled. (Note: Could not refresh all details from Zoom.)", + ) - return JsonResponse({ - 'success': True, - 'message': 'Meeting rescheduled successfully!', - 'join_url': zoom_meeting.join_url, - 'new_interview_datetime': new_start_time.strftime("%Y-%m-%d %H:%M") - }) + return JsonResponse( + { + "success": True, + "message": "Meeting rescheduled successfully!", + "join_url": zoom_meeting.join_url, + "new_interview_datetime": new_start_time.strftime("%Y-%m-%d %H:%M"), + } + ) else: messages.error(request, result["message"]) - return JsonResponse({'success': False, 'error': result["message"]}, status=400) + return JsonResponse({"success": False, "error": result["message"]}, status=400) # The original schedule_candidate_meeting and reschedule_candidate_meeting (without api_ prefix) @@ -2069,12 +2417,12 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): Handles POST to process the rescheduling of a meeting. """ job = get_object_or_404(JobPosting, slug=job_slug) - candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) + application = get_object_or_404(Application, pk=candidate_pk, job=job) scheduled_interview = get_object_or_404( - ScheduledInterview.objects.select_related('zoom_meeting'), + ScheduledInterview.objects.select_related("zoom_meeting"), pk=interview_pk, - candidate=candidate, - job=job + application=application, + job=job, ) zoom_meeting = scheduled_interview.zoom_meeting @@ -2084,7 +2432,7 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): # If candidate.has_future_meeting is True, it implies they have at least one other upcoming meeting, # or the specific meeting being rescheduled is itself in the future. # We can refine this logic if needed, e.g., check for meetings *other than* the current `interview_pk`. - has_other_future_meetings = candidate.has_future_meeting + has_other_future_meetings = application.has_future_meeting # More precise check: if the current meeting being rescheduled is in the future, then by definition # the candidate will have a future meeting (this one). The UI might want to know if there are *others*. # For now, `candidate.has_future_meeting` is a good general indicator. @@ -2092,29 +2440,42 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): if request.method == "POST": form = ZoomMeetingForm(request.POST) if form.is_valid(): - new_topic = form.cleaned_data.get('topic') - new_start_time = form.cleaned_data.get('start_time') - new_duration = form.cleaned_data.get('duration') + new_topic = form.cleaned_data.get("topic") + new_start_time = form.cleaned_data.get("start_time") + new_duration = form.cleaned_data.get("duration") # Use a default topic if not provided, keeping with the original structure if not new_topic: - new_topic = f"Interview: {job.title} with {candidate.name}" + new_topic = f"Interview: {job.title} with {application.name}" # Ensure new_start_time is in the future if new_start_time <= timezone.now(): messages.error(request, "Start time must be in the future.") # Re-render form with error and initial data - return render(request, "recruitment/schedule_meeting_form.html", { # Reusing the same form template - 'form': form, - 'job': job, - 'candidate': candidate, - 'scheduled_interview': scheduled_interview, - 'initial_topic': new_topic, - 'initial_start_time': new_start_time.strftime('%Y-%m-%dT%H:%M') if new_start_time else '', - 'initial_duration': new_duration, - 'action_url': reverse('reschedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk, 'interview_pk': interview_pk}), - 'has_future_meeting': has_other_future_meetings # Pass status for template - }) + return render( + request, + "recruitment/schedule_meeting_form.html", + { # Reusing the same form template + "form": form, + "job": job, + "application": application, + "scheduled_interview": scheduled_interview, + "initial_topic": new_topic, + "initial_start_time": new_start_time.strftime("%Y-%m-%dT%H:%M") + if new_start_time + else "", + "initial_duration": new_duration, + "action_url": reverse( + "reschedule_candidate_meeting", + kwargs={ + "job_slug": job_slug, + "candidate_pk": candidate_pk, + "interview_pk": interview_pk, + }, + ), + "has_future_meeting": has_other_future_meetings, # Pass status for template + }, + ) # Prepare data for Zoom API update # The update_zoom_meeting expects start_time as ISO string with 'Z' @@ -2125,7 +2486,9 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): } # Update Zoom meeting using utility function - zoom_update_result = update_zoom_meeting(zoom_meeting.meeting_id, zoom_update_data) + zoom_update_result = update_zoom_meeting( + zoom_meeting.meeting_id, zoom_update_data + ) if zoom_update_result["status"] == "success": # Fetch the latest details from Zoom after successful update @@ -2135,20 +2498,35 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): updated_zoom_details = details_result["meeting_details"] # Update local ZoomMeeting record zoom_meeting.topic = updated_zoom_details.get("topic", new_topic) - zoom_meeting.start_time = new_start_time # Store the original datetime + zoom_meeting.start_time = ( + new_start_time # Store the original datetime + ) zoom_meeting.duration = new_duration - zoom_meeting.join_url = updated_zoom_details.get("join_url", zoom_meeting.join_url) - zoom_meeting.password = updated_zoom_details.get("password", zoom_meeting.password) - zoom_meeting.status = updated_zoom_details.get("status", zoom_meeting.status) - zoom_meeting.zoom_gateway_response = details_result.get("meeting_details") + zoom_meeting.join_url = updated_zoom_details.get( + "join_url", zoom_meeting.join_url + ) + zoom_meeting.password = updated_zoom_details.get( + "password", zoom_meeting.password + ) + zoom_meeting.status = updated_zoom_details.get( + "status", zoom_meeting.status + ) + zoom_meeting.zoom_gateway_response = details_result.get( + "meeting_details" + ) zoom_meeting.save() # Update ScheduledInterview record scheduled_interview.interview_date = new_start_time.date() scheduled_interview.interview_time = new_start_time.time() - scheduled_interview.status = 'rescheduled' # Or 'scheduled' if you prefer + scheduled_interview.status = ( + "rescheduled" # Or 'scheduled' if you prefer + ) scheduled_interview.save() - messages.success(request, f"Meeting for {candidate.name} rescheduled successfully.") + messages.success( + request, + f"Meeting for {application.name} rescheduled successfully.", + ) else: # If fetching details fails, update with form data and log a warning logger.warning( @@ -2163,52 +2541,98 @@ def reschedule_candidate_meeting(request, job_slug, candidate_pk, interview_pk): scheduled_interview.interview_date = new_start_time.date() scheduled_interview.interview_time = new_start_time.time() scheduled_interview.save() - messages.success(request, f"Meeting for {candidate.name} rescheduled. (Note: Could not refresh all details from Zoom.)") + messages.success( + request, + f"Meeting for {application.name} rescheduled. (Note: Could not refresh all details from Zoom.)", + ) - return redirect('candidate_interview_view', slug=job.slug) + return redirect("candidate_interview_view", slug=job.slug) else: - messages.error(request, f"Failed to update Zoom meeting: {zoom_update_result['message']}") + messages.error( + request, + f"Failed to update Zoom meeting: {zoom_update_result['message']}", + ) # Re-render form with error - return render(request, "recruitment/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'scheduled_interview': scheduled_interview, - 'initial_topic': new_topic, - 'initial_start_time': new_start_time.strftime('%Y-%m-%dT%H:%M') if new_start_time else '', - 'initial_duration': new_duration, - 'action_url': reverse('reschedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk, 'interview_pk': interview_pk}), - 'has_future_meeting': has_other_future_meetings - }) + return render( + request, + "recruitment/schedule_meeting_form.html", + { + "form": form, + "job": job, + "application": application, + "scheduled_interview": scheduled_interview, + "initial_topic": new_topic, + "initial_start_time": new_start_time.strftime("%Y-%m-%dT%H:%M") + if new_start_time + else "", + "initial_duration": new_duration, + "action_url": reverse( + "reschedule_candidate_meeting", + kwargs={ + "job_slug": job_slug, + "candidate_pk": candidate_pk, + "interview_pk": interview_pk, + }, + ), + "has_future_meeting": has_other_future_meetings, + }, + ) else: # Form validation errors - return render(request, "recruitment/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'scheduled_interview': scheduled_interview, - 'initial_topic': request.POST.get('topic', new_topic), - 'initial_start_time': request.POST.get('start_time', new_start_time.strftime('%Y-%m-%dT%H:%M') if new_start_time else ''), - 'initial_duration': request.POST.get('duration', new_duration), - 'action_url': reverse('reschedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk, 'interview_pk': interview_pk}), - 'has_future_meeting': has_other_future_meetings - }) - else: # GET request + return render( + request, + "recruitment/schedule_meeting_form.html", + { + "form": form, + "job": job, + "application": application, + "scheduled_interview": scheduled_interview, + "initial_topic": request.POST.get("topic", new_topic), + "initial_start_time": request.POST.get( + "start_time", + new_start_time.strftime("%Y-%m-%dT%H:%M") + if new_start_time + else "", + ), + "initial_duration": request.POST.get("duration", new_duration), + "action_url": reverse( + "reschedule_candidate_meeting", + kwargs={ + "job_slug": job_slug, + "candidate_pk": candidate_pk, + "interview_pk": interview_pk, + }, + ), + "has_future_meeting": has_other_future_meetings, + }, + ) + else: # GET request # Pre-populate form with existing meeting details initial_data = { - 'topic': zoom_meeting.topic, - 'start_time': zoom_meeting.start_time.strftime('%Y-%m-%dT%H:%M'), - 'duration': zoom_meeting.duration, + "topic": zoom_meeting.topic, + "start_time": zoom_meeting.start_time.strftime("%Y-%m-%dT%H:%M"), + "duration": zoom_meeting.duration, } form = ZoomMeetingForm(initial=initial_data) - return render(request, "recruitment/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'scheduled_interview': scheduled_interview, # Pass to template for title/differentiation - 'action_url': reverse('reschedule_candidate_meeting', kwargs={'job_slug': job_slug, 'candidate_pk': candidate_pk, 'interview_pk': interview_pk}), - 'has_future_meeting': has_other_future_meetings # Pass status for template - }) + return render( + request, + "recruitment/schedule_meeting_form.html", + { + "form": form, + "job": job, + "application": application, + "scheduled_interview": scheduled_interview, # Pass to template for title/differentiation + "action_url": reverse( + "reschedule_candidate_meeting", + kwargs={ + "job_slug": job_slug, + "candidate_pk": candidate_pk, + "interview_pk": interview_pk, + }, + ), + "has_future_meeting": has_other_future_meetings, # Pass status for template + }, + ) def schedule_meeting_for_candidate(request, slug, candidate_pk): @@ -2217,14 +2641,14 @@ def schedule_meeting_for_candidate(request, slug, candidate_pk): Handles POST to process the form, create a meeting, and redirect back. """ job = get_object_or_404(JobPosting, slug=slug) - candidate = get_object_or_404(Candidate, pk=candidate_pk, job=job) + candidate = get_object_or_404(Application, pk=candidate_pk, job=job) if request.method == "POST": form = ZoomMeetingForm(request.POST) if form.is_valid(): - topic_val = form.cleaned_data.get('topic') - start_time_val = form.cleaned_data.get('start_time') - duration_val = form.cleaned_data.get('duration') + topic_val = form.cleaned_data.get("topic") + start_time_val = form.cleaned_data.get("start_time") + duration_val = form.cleaned_data.get("duration") # Use a default topic if not provided if not topic_val: @@ -2234,7 +2658,7 @@ def schedule_meeting_for_candidate(request, slug, candidate_pk): if start_time_val <= timezone.now(): messages.error(request, "Start time must be in the future.") # Re-render form with error and initial data - return redirect('candidate_interview_view', slug=job.slug) + return redirect("candidate_interview_view", slug=job.slug) # return render(request, "recruitment/schedule_meeting_form.html", { # 'form': form, # 'job': job, @@ -2249,94 +2673,120 @@ def schedule_meeting_for_candidate(request, slug, candidate_pk): # and handles its own conversion to UTC for the API call. zoom_creation_result = create_zoom_meeting( topic=topic_val, - start_time=start_time_val, # Pass the datetime object - duration=duration_val + start_time=start_time_val, # Pass the datetime object + duration=duration_val, ) if zoom_creation_result["status"] == "success": zoom_details = zoom_creation_result["meeting_details"] zoom_meeting_instance = ZoomMeeting.objects.create( topic=topic_val, - start_time=start_time_val, # Store the original datetime + start_time=start_time_val, # Store the original datetime duration=duration_val, meeting_id=zoom_details["meeting_id"], join_url=zoom_details["join_url"], - password=zoom_details.get("password"), # password might be None - status=zoom_creation_result["zoom_gateway_response"].get("status", "waiting"), + password=zoom_details.get("password"), # password might be None + status=zoom_creation_result["zoom_gateway_response"].get( + "status", "waiting" + ), zoom_gateway_response=zoom_creation_result["zoom_gateway_response"], ) # Create a ScheduledInterview record ScheduledInterview.objects.create( - candidate=candidate, + application=candidate, job=job, zoom_meeting=zoom_meeting_instance, interview_date=start_time_val.date(), interview_time=start_time_val.time(), - status='scheduled' + status="scheduled", ) messages.success(request, f"Meeting scheduled with {candidate.name}.") - return redirect('candidate_interview_view', slug=job.slug) + return redirect("candidate_interview_view", slug=job.slug) else: - messages.error(request, f"Failed to create Zoom meeting: {zoom_creation_result['message']}") + messages.error( + request, + f"Failed to create Zoom meeting: {zoom_creation_result['message']}", + ) # Re-render form with error - return render(request, "recruitment/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'initial_topic': topic_val, - 'initial_start_time': start_time_val.strftime('%Y-%m-%dT%H:%M') if start_time_val else '', - 'initial_duration': duration_val - }) + return render( + request, + "recruitment/schedule_meeting_form.html", + { + "form": form, + "job": job, + "candidate": candidate, + "initial_topic": topic_val, + "initial_start_time": start_time_val.strftime("%Y-%m-%dT%H:%M") + if start_time_val + else "", + "initial_duration": duration_val, + }, + ) else: # Form validation errors - return render(request, "meetings/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate, - 'initial_topic': request.POST.get('topic', f"Interview: {job.title} with {candidate.name}"), - 'initial_start_time': request.POST.get('start_time', ''), - 'initial_duration': request.POST.get('duration', 60) - }) - else: # GET request + return render( + request, + "meetings/schedule_meeting_form.html", + { + "form": form, + "job": job, + "candidate": candidate, + "initial_topic": request.POST.get( + "topic", f"Interview: {job.title} with {candidate.name}" + ), + "initial_start_time": request.POST.get("start_time", ""), + "initial_duration": request.POST.get("duration", 60), + }, + ) + else: # GET request initial_data = { - 'topic': f"Interview: {job.title} with {candidate.name}", - 'start_time': (timezone.now() + timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M'), # Default to 1 hour from now - 'duration': 60, # Default duration + "topic": f"Interview: {job.title} with {candidate.name}", + "start_time": (timezone.now() + timedelta(hours=1)).strftime( + "%Y-%m-%dT%H:%M" + ), # Default to 1 hour from now + "duration": 60, # Default duration } form = ZoomMeetingForm(initial=initial_data) - return render(request, "meetings/schedule_meeting_form.html", { - 'form': form, - 'job': job, - 'candidate': candidate - }) + return render( + request, + "meetings/schedule_meeting_form.html", + {"form": form, "job": job, "candidate": candidate}, + ) from django.core.exceptions import ObjectDoesNotExist + def user_profile_image_update(request, pk): user = get_object_or_404(User, pk=pk) try: - instance =user.profile + instance = user.profile except ObjectDoesNotExist as e: Profile.objects.create(user=user) - if request.method == 'POST': - profile_form = ProfileImageUploadForm(request.POST, request.FILES, instance=user.profile) + if request.method == "POST": + profile_form = ProfileImageUploadForm( + request.POST, request.FILES, instance=user.profile + ) if profile_form.is_valid(): profile_form.save() - messages.success(request, 'Image uploaded successfully') - return redirect('user_detail', pk=user.pk) + messages.success(request, "Image uploaded successfully") + return redirect("user_detail", pk=user.pk) else: - messages.error(request, 'An error occurred while uploading image. Please check the errors below.') + messages.error( + request, + "An error occurred while uploading image. Please check the errors below.", + ) else: profile_form = ProfileImageUploadForm(instance=user.profile) context = { - 'profile_form': profile_form, - 'user': user, + "profile_form": profile_form, + "user": user, } - return render(request, 'user/profile.html', context) + return render(request, "user/profile.html", context) + def user_detail(request, pk): user = get_object_or_404(User, pk=pk) @@ -2347,23 +2797,16 @@ def user_detail(request, pk): except: profile_form = ProfileImageUploadForm() - if request.method == 'POST': - first_name=request.POST.get('first_name') - last_name=request.POST.get('last_name') + if request.method == "POST": + first_name = request.POST.get("first_name") + last_name = request.POST.get("last_name") if first_name: - user.first_name=first_name + user.first_name = first_name if last_name: - user.last_name=last_name + user.last_name = last_name user.save() - context = { - - 'user': user, - 'profile_form':profile_form - - } - return render(request, 'user/profile.html', context) - - + context = {"user": user, "profile_form": profile_form} + return render(request, "user/profile.html", context) def easy_logs(request): @@ -2372,56 +2815,50 @@ def easy_logs(request): """ logs_per_page = 20 + active_tab = request.GET.get("tab", "crud") - active_tab = request.GET.get('tab', 'crud') - - if active_tab == 'login': - queryset = LoginEvent.objects.order_by('-datetime') + if active_tab == "login": + queryset = LoginEvent.objects.order_by("-datetime") tab_title = _("User Authentication") - elif active_tab == 'request': - queryset = RequestEvent.objects.order_by('-datetime') + elif active_tab == "request": + queryset = RequestEvent.objects.order_by("-datetime") tab_title = _("HTTP Requests") else: - queryset = CRUDEvent.objects.order_by('-datetime') + queryset = CRUDEvent.objects.order_by("-datetime") tab_title = _("Model Changes (CRUD)") - active_tab = 'crud' - + active_tab = "crud" paginator = Paginator(queryset, logs_per_page) - page = request.GET.get('page') + page = request.GET.get("page") try: - logs_page = paginator.page(page) except PageNotAnInteger: - logs_page = paginator.page(1) except EmptyPage: - logs_page = paginator.page(paginator.num_pages) context = { - 'logs': logs_page, - 'total_count': queryset.count(), - 'active_tab': active_tab, - 'tab_title': tab_title, + "logs": logs_page, + "total_count": queryset.count(), + "active_tab": active_tab, + "tab_title": tab_title, } return render(request, "includes/easy_logs.html", context) - from allauth.account.views import SignupView from django.contrib.auth.decorators import user_passes_test + def is_superuser_check(user): return user.is_superuser -@user_passes_test(is_superuser_check) +@staff_user_required def create_staff_user(request): - if request.method == 'POST': - + if request.method == "POST": form = StaffUserCreationForm(request.POST) print(form) if form.is_valid(): @@ -2429,68 +2866,70 @@ def create_staff_user(request): messages.success( request, f"Staff user {form.cleaned_data['first_name']} {form.cleaned_data['last_name']} " - f"({form.cleaned_data['email']}) created successfully!" + f"({form.cleaned_data['email']}) created successfully!", ) - return redirect('admin_settings') + return redirect("admin_settings") else: form = StaffUserCreationForm() - return render(request, 'user/create_staff.html', {'form': form}) + return render(request, "user/create_staff.html", {"form": form}) - - -@user_passes_test(is_superuser_check) +@staff_user_required def admin_settings(request): - staffs=User.objects.filter(is_superuser=False) + staffs = User.objects.filter(is_superuser=False) form = ToggleAccountForm() - context={ - 'staffs':staffs, - 'form':form - } - return render(request,'user/admin_settings.html',context) + context = {"staffs": staffs, "form": form} + return render(request, "user/admin_settings.html", context) from django.contrib.auth.forms import SetPasswordForm -@user_passes_test(is_superuser_check) -def set_staff_password(request,pk): - user=get_object_or_404(User,pk=pk) + +@staff_user_required +def set_staff_password(request, pk): + user = get_object_or_404(User, pk=pk) print(request.POST) - if request.method=='POST': + if request.method == "POST": form = SetPasswordForm(user, data=request.POST) if form.is_valid(): - form.save() - messages.success(request,f'Password successfully changed') - return redirect('admin_settings') + form.save() + messages.success(request, f"Password successfully changed") + return redirect("admin_settings") else: - form=SetPasswordForm(user=user) - messages.error(request,f'Password does not match please try again.') - return redirect('admin_settings') + form = SetPasswordForm(user=user) + messages.error(request, f"Password does not match please try again.") + return redirect("admin_settings") else: - form=SetPasswordForm(user=user) - return render(request,'user/staff_password_create.html',{'form':form,'user':user}) + form = SetPasswordForm(user=user) + return render( + request, "user/staff_password_create.html", {"form": form, "user": user} + ) -@user_passes_test(is_superuser_check) -def account_toggle_status(request,pk): - user=get_object_or_404(User,pk=pk) - if request.method=='POST': +@staff_user_required +def account_toggle_status(request, pk): + user = get_object_or_404(User, pk=pk) + if request.method == "POST": print(user.is_active) - form=ToggleAccountForm(request.POST) + form = ToggleAccountForm(request.POST) if form.is_valid(): if user.is_active: - user.is_active=False + user.is_active = False user.save() - messages.success(request,f'Staff with email: {user.email} deactivated successfully') - return redirect('admin_settings') + messages.success( + request, f"Staff with email: {user.email} deactivated successfully" + ) + return redirect("admin_settings") else: - user.is_active=True + user.is_active = True user.save() - messages.success(request,f'Staff with email: {user.email} activated successfully') - return redirect('admin_settings') + messages.success( + request, f"Staff with email: {user.email} activated successfully" + ) + return redirect("admin_settings") else: - messages.error(f'Please correct the error below') + messages.error(f"Please correct the error below") # @login_required @@ -2500,12 +2939,13 @@ def account_toggle_status(request,pk): @csrf_exempt +@staff_user_required def zoom_webhook_view(request): print(request.headers) print(settings.ZOOM_WEBHOOK_API_KEY) # if api_key != settings.ZOOM_WEBHOOK_API_KEY: # return HttpResponse(status=405) - if request.method == 'POST': + if request.method == "POST": try: payload = json.loads(request.body) async_task("recruitment.tasks.handle_zoom_webhook_event", payload) @@ -2516,46 +2956,48 @@ def zoom_webhook_view(request): # Meeting Comments Views -@login_required +@staff_user_required def add_meeting_comment(request, slug): """Add a comment to a meeting""" meeting = get_object_or_404(ZoomMeeting, slug=slug) - if request.method == 'POST': + if request.method == "POST": form = MeetingCommentForm(request.POST) if form.is_valid(): comment = form.save(commit=False) comment.meeting = meeting comment.author = request.user comment.save() - messages.success(request, 'Comment added successfully!') + messages.success(request, "Comment added successfully!") # HTMX response - return just the comment section - if 'HX-Request' in request.headers: - return render(request, 'includes/comment_list.html', { - 'comments': meeting.comments.all().order_by('-created_at'), - 'meeting': meeting - }) + if "HX-Request" in request.headers: + return render( + request, + "includes/comment_list.html", + { + "comments": meeting.comments.all().order_by("-created_at"), + "meeting": meeting, + }, + ) - return redirect('meeting_details', slug=slug) + return redirect("meeting_details", slug=slug) else: form = MeetingCommentForm() context = { - 'form': form, - 'meeting': meeting, + "form": form, + "meeting": meeting, } # HTMX response - return the comment form - if 'HX-Request' in request.headers: - return render(request, 'includes/comment_form.html', context) + if "HX-Request" in request.headers: + return render(request, "includes/comment_form.html", context) - return redirect('meeting_details', slug=slug) + return redirect("meeting_details", slug=slug) - - -@login_required +@staff_user_required def edit_meeting_comment(request, slug, comment_id): """Edit a meeting comment""" meeting = get_object_or_404(ZoomMeeting, slug=slug) @@ -2563,35 +3005,35 @@ def edit_meeting_comment(request, slug, comment_id): # Check if user is author if comment.author != request.user and not request.user.is_staff: - messages.error(request, 'You can only edit your own comments.') - return redirect('meeting_details', slug=slug) + messages.error(request, "You can only edit your own comments.") + return redirect("meeting_details", slug=slug) - if request.method == 'POST': + if request.method == "POST": form = MeetingCommentForm(request.POST, instance=comment) if form.is_valid(): comment = form.save() - messages.success(request, 'Comment updated successfully!') + messages.success(request, "Comment updated successfully!") # HTMX response - return just comment section - if 'HX-Request' in request.headers: - return render(request, 'includes/comment_list.html', { - 'comments': meeting.comments.all().order_by('-created_at'), - 'meeting': meeting - }) + if "HX-Request" in request.headers: + return render( + request, + "includes/comment_list.html", + { + "comments": meeting.comments.all().order_by("-created_at"), + "meeting": meeting, + }, + ) - return redirect('meeting_details', slug=slug) + return redirect("meeting_details", slug=slug) else: form = MeetingCommentForm(instance=comment) - context = { - 'form': form, - 'meeting': meeting, - 'comment': comment - } - return render(request, 'includes/edit_comment_form.html', context) + context = {"form": form, "meeting": meeting, "comment": comment} + return render(request, "includes/edit_comment_form.html", context) -@login_required +@staff_user_required def delete_meeting_comment(request, slug, comment_id): """Delete a meeting comment""" meeting = get_object_or_404(ZoomMeeting, slug=slug) @@ -2599,37 +3041,48 @@ def delete_meeting_comment(request, slug, comment_id): # Check if user is the author if comment.author != request.user and not request.user.is_staff: - messages.error(request, 'You can only delete your own comments.') - return redirect('meeting_details', slug=slug) + messages.error(request, "You can only delete your own comments.") + return redirect("meeting_details", slug=slug) - if request.method == 'POST': + if request.method == "POST": comment.delete() - messages.success(request, 'Comment deleted successfully!') + messages.success(request, "Comment deleted successfully!") # HTMX response - return just the comment section - if 'HX-Request' in request.headers: - return render(request, 'includes/comment_list.html', { - 'comments': meeting.comments.all().order_by('-created_at'), - 'meeting': meeting - }) + if "HX-Request" in request.headers: + return render( + request, + "includes/comment_list.html", + { + "comments": meeting.comments.all().order_by("-created_at"), + "meeting": meeting, + }, + ) - return redirect('meeting_details', slug=slug) + return redirect("meeting_details", slug=slug) # HTMX response - return the delete confirmation modal - if 'HX-Request' in request.headers: - return render(request, 'includes/delete_comment_form.html', { - 'meeting': meeting, - 'comment': comment, - 'delete_url': reverse('delete_meeting_comment', kwargs={'slug': slug, 'comment_id': comment_id}) - }) + if "HX-Request" in request.headers: + return render( + request, + "includes/delete_comment_form.html", + { + "meeting": meeting, + "comment": comment, + "delete_url": reverse( + "delete_meeting_comment", + kwargs={"slug": slug, "comment_id": comment_id}, + ), + }, + ) - return redirect('meeting_details', slug=slug) + return redirect("meeting_details", slug=slug) -@login_required -def set_meeting_candidate(request,slug): +@staff_user_required +def set_meeting_candidate(request, slug): meeting = get_object_or_404(ZoomMeeting, slug=slug) - if request.method == 'POST' and 'HX-Request' not in request.headers: + if request.method == "POST" and "HX-Request" not in request.headers: form = InterviewForm(request.POST) if form.is_valid(): candidate = form.save(commit=False) @@ -2637,154 +3090,155 @@ def set_meeting_candidate(request,slug): candidate.interview_date = meeting.start_time.date() candidate.interview_time = meeting.start_time.time() candidate.save() - messages.success(request, 'Candidate added successfully!') - return redirect('list_meetings') + messages.success(request, "Candidate added successfully!") + return redirect("list_meetings") job = request.GET.get("job") form = InterviewForm() if job: - form.fields['candidate'].queryset = Candidate.objects.filter(job=job) + form.fields["candidate"].queryset = Application.objects.filter(job=job) else: - form.fields['candidate'].queryset = Candidate.objects.none() - form.fields['job'].widget.attrs.update({ - 'hx-get': reverse('set_meeting_candidate', kwargs={'slug': slug}), - 'hx-target': '#div_id_candidate', - 'hx-select': '#div_id_candidate', - 'hx-swap': 'outerHTML' - }) - context = { - "form": form, - "meeting": meeting - } - return render(request, 'meetings/set_candidate_form.html', context) + form.fields["candidate"].queryset = Application.objects.none() + form.fields["job"].widget.attrs.update( + { + "hx-get": reverse("set_meeting_candidate", kwargs={"slug": slug}), + "hx-target": "#div_id_candidate", + "hx-select": "#div_id_candidate", + "hx-swap": "outerHTML", + } + ) + context = {"form": form, "meeting": meeting} + return render(request, "meetings/set_candidate_form.html", context) # Hiring Agency CRUD Views -@login_required +@staff_user_required def agency_list(request): """List all hiring agencies with search and pagination""" - search_query = request.GET.get('q', '') + search_query = request.GET.get("q", "") agencies = HiringAgency.objects.all() if search_query: agencies = agencies.filter( - Q(name__icontains=search_query) | - Q(contact_person__icontains=search_query) | - Q(email__icontains=search_query) | - Q(country__icontains=search_query) + Q(name__icontains=search_query) + | Q(contact_person__icontains=search_query) + | Q(email__icontains=search_query) + | Q(country__icontains=search_query) ) # Order by most recently created - agencies = agencies.order_by('-created_at') + agencies = agencies.order_by("-created_at") # Pagination paginator = Paginator(agencies, 10) # Show 10 agencies per page - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context = { - 'page_obj': page_obj, - 'search_query': search_query, - 'total_agencies': agencies.count(), + "page_obj": page_obj, + "search_query": search_query, + "total_agencies": agencies.count(), } - return render(request, 'recruitment/agency_list.html', context) + return render(request, "recruitment/agency_list.html", context) -@login_required +@staff_user_required def agency_create(request): """Create a new hiring agency""" - if request.method == 'POST': + if request.method == "POST": form = HiringAgencyForm(request.POST) if form.is_valid(): agency = form.save() messages.success(request, f'Agency "{agency.name}" created successfully!') - return redirect('agency_detail', slug=agency.slug) + return redirect("agency_detail", slug=agency.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = HiringAgencyForm() context = { - 'form': form, - 'title': 'Create New Agency', - 'button_text': 'Create Agency', + "form": form, + "title": "Create New Agency", + "button_text": "Create Agency", } - return render(request, 'recruitment/agency_form.html', context) + return render(request, "recruitment/agency_form.html", context) -@login_required +@staff_user_required def agency_detail(request, slug): """View details of a specific hiring agency""" agency = get_object_or_404(HiringAgency, slug=slug) # Get candidates associated with this agency - candidates = Candidate.objects.filter(hiring_agency=agency).order_by('-created_at') + candidates = Application.objects.filter(hiring_agency=agency).order_by("-created_at") # Statistics total_candidates = candidates.count() - active_candidates = candidates.filter(stage__in=['Applied', 'Screening', 'Exam', 'Interview', 'Offer']).count() - hired_candidates = candidates.filter(stage='Hired').count() - rejected_candidates = candidates.filter(stage='Rejected').count() + active_candidates = candidates.filter( + stage__in=["Applied", "Screening", "Exam", "Interview", "Offer"] + ).count() + hired_candidates = candidates.filter(stage="Hired").count() + rejected_candidates = candidates.filter(stage="Rejected").count() context = { - 'agency': agency, - 'candidates': candidates[:10], # Show recent 10 candidates - 'total_candidates': total_candidates, - 'active_candidates': active_candidates, - 'hired_candidates': hired_candidates, - 'rejected_candidates': rejected_candidates, + "agency": agency, + "candidates": candidates[:10], # Show recent 10 candidates + "total_candidates": total_candidates, + "active_candidates": active_candidates, + "hired_candidates": hired_candidates, + "rejected_candidates": rejected_candidates, } - return render(request, 'recruitment/agency_detail.html', context) + return render(request, "recruitment/agency_detail.html", context) -@login_required +@staff_user_required def agency_update(request, slug): """Update an existing hiring agency""" agency = get_object_or_404(HiringAgency, slug=slug) - if request.method == 'POST': + if request.method == "POST": form = HiringAgencyForm(request.POST, instance=agency) if form.is_valid(): agency = form.save() messages.success(request, f'Agency "{agency.name}" updated successfully!') - return redirect('agency_detail', slug=agency.slug) + return redirect("agency_detail", slug=agency.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = HiringAgencyForm(instance=agency) context = { - 'form': form, - 'agency': agency, - 'title': f'Edit Agency: {agency.name}', - 'button_text': 'Update Agency', + "form": form, + "agency": agency, + "title": f"Edit Agency: {agency.name}", + "button_text": "Update Agency", } - return render(request, 'recruitment/agency_form.html', context) + return render(request, "recruitment/agency_form.html", context) -@login_required +@staff_user_required def agency_delete(request, slug): """Delete a hiring agency""" agency = get_object_or_404(HiringAgency, slug=slug) - if request.method == 'POST': + if request.method == "POST": agency_name = agency.name agency.delete() messages.success(request, f'Agency "{agency_name}" deleted successfully!') - return redirect('agency_list') + return redirect("agency_list") context = { - 'agency': agency, - 'title': 'Delete Agency', - 'message': f'Are you sure you want to delete the agency "{agency.name}"?', - 'cancel_url': reverse('agency_detail', kwargs={'slug': agency.slug}), + "agency": agency, + "title": "Delete Agency", + "message": f'Are you sure you want to delete the agency "{agency.name}"?', + "cancel_url": reverse("agency_detail", kwargs={"slug": agency.slug}), } - return render(request, 'recruitment/agency_confirm_delete.html', context) + return render(request, "recruitment/agency_confirm_delete.html", context) # Notification Views -# @login_required +# @staff_user_required # def notification_list(request): # """List all notifications for the current user""" # # Get filter parameters @@ -2830,7 +3284,7 @@ def agency_delete(request, slug): # return render(request, 'recruitment/notification_list.html', context) -# @login_required +# @staff_user_required # def notification_detail(request, notification_id): # """View details of a specific notification""" # notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) @@ -2846,7 +3300,7 @@ def agency_delete(request, slug): # return render(request, 'recruitment/notification_detail.html', context) -# @login_required +# @staff_user_required # def notification_mark_read(request, notification_id): # """Mark a notification as read""" # notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) @@ -2861,7 +3315,7 @@ def agency_delete(request, slug): # return redirect('notification_list') -# @login_required +# @staff_user_required # def notification_mark_unread(request, notification_id): # """Mark a notification as unread""" # notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) @@ -2876,7 +3330,7 @@ def agency_delete(request, slug): # return redirect('notification_list') -# @login_required +# @staff_user_required # def notification_delete(request, notification_id): # """Delete a notification""" # notification = get_object_or_404(Notification, id=notification_id, recipient=request.user) @@ -2896,7 +3350,7 @@ def agency_delete(request, slug): # return render(request, 'recruitment/notification_confirm_delete.html', context) -# @login_required +# @staff_user_required # def notification_mark_all_read(request): # """Mark all notifications as read for the current user""" # if request.method == 'POST': @@ -2923,7 +3377,7 @@ def agency_delete(request, slug): # return render(request, 'recruitment/notification_confirm_all_read.html', context) -# @login_required +# @staff_user_required # def api_notification_count(request): # """API endpoint to get unread notification count and recent notifications""" # # Get unread notifications @@ -2972,7 +3426,7 @@ def agency_delete(request, slug): # }) -# @login_required +# @staff_user_required # def notification_stream(request): # """SSE endpoint for real-time notifications""" # from django.http import StreamingHttpResponse @@ -3085,23 +3539,23 @@ def agency_delete(request, slug): # response['X-Accel-Buffering'] = 'no' # Disable buffering for nginx # response['Connection'] = 'keep-alive' - # context = { - # 'agency': agency, - # 'page_obj': page_obj, - # 'stage_filter': stage_filter, - # 'total_candidates': candidates.count(), - # } - # return render(request, 'recruitment/agency_candidates.html', context) +# context = { +# 'agency': agency, +# 'page_obj': page_obj, +# 'stage_filter': stage_filter, +# 'total_candidates': candidates.count(), +# } +# return render(request, 'recruitment/agency_candidates.html', context) -@login_required +@staff_user_required def agency_candidates(request, slug): """View all candidates from a specific agency""" agency = get_object_or_404(HiringAgency, slug=slug) - candidates = Candidate.objects.filter(hiring_agency=agency).order_by('-created_at') + candidates = Application.objects.filter(hiring_agency=agency).order_by("-created_at") # Filter by stage if provided - stage_filter = request.GET.get('stage') + stage_filter = request.GET.get("stage") if stage_filter: candidates = candidates.filter(stage=stage_filter) @@ -3110,35 +3564,33 @@ def agency_candidates(request, slug): # Pagination paginator = Paginator(candidates, 20) # Show 20 candidates per page - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context = { - 'agency': agency, - 'page_obj': page_obj, - 'stage_filter': stage_filter, - 'total_candidates': total_candidates, + "agency": agency, + "page_obj": page_obj, + "stage_filter": stage_filter, + "total_candidates": total_candidates, } - return render(request, 'recruitment/agency_candidates.html', context) - - + return render(request, "recruitment/agency_candidates.html", context) # Agency Portal Management Views -@login_required +@staff_user_required def agency_assignment_list(request): """List all agency job assignments""" - search_query = request.GET.get('q', '') - status_filter = request.GET.get('status', '') + search_query = request.GET.get("q", "") + status_filter = request.GET.get("status", "") - assignments = AgencyJobAssignment.objects.select_related( - 'agency', 'job' - ).order_by('-created_at') + assignments = AgencyJobAssignment.objects.select_related("agency", "job").order_by( + "-created_at" + ) if search_query: assignments = assignments.filter( - Q(agency__name__icontains=search_query) | - Q(job__title__icontains=search_query) + Q(agency__name__icontains=search_query) + | Q(job__title__icontains=search_query) ) if status_filter: @@ -3146,355 +3598,507 @@ def agency_assignment_list(request): # Pagination paginator = Paginator(assignments, 15) # Show 15 assignments per page - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context = { - 'page_obj': page_obj, - 'search_query': search_query, - 'status_filter': status_filter, - 'total_assignments': assignments.count(), + "page_obj": page_obj, + "search_query": search_query, + "status_filter": status_filter, + "total_assignments": assignments.count(), } - return render(request, 'recruitment/agency_assignment_list.html', context) + return render(request, "recruitment/agency_assignment_list.html", context) -@login_required -def agency_assignment_create(request,slug=None): +@staff_user_required +def agency_assignment_create(request, slug=None): """Create a new agency job assignment""" agency = HiringAgency.objects.get(slug=slug) if slug else None - if request.method == 'POST': + if request.method == "POST": form = AgencyJobAssignmentForm(request.POST) # if agency: # form.instance.agency = agency if form.is_valid(): assignment = form.save() - messages.success(request, f'Assignment created for {assignment.agency.name} - {assignment.job.title}!') - return redirect('agency_assignment_detail', slug=assignment.slug) + messages.success( + request, + f"Assignment created for {assignment.agency.name} - {assignment.job.title}!", + ) + return redirect("agency_assignment_detail", slug=assignment.slug) else: - messages.error(request, f'Please correct the errors below.{form.errors.as_text()}') + messages.error( + request, f"Please correct the errors below.{form.errors.as_text()}" + ) print(form.errors.as_json()) else: form = AgencyJobAssignmentForm() try: # from django.forms import HiddenInput - form.initial['agency'] = agency + form.initial["agency"] = agency # form.fields['agency'].widget = HiddenInput() except HiringAgency.DoesNotExist: pass context = { - 'form': form, - 'title': 'Create New Assignment', - 'button_text': 'Create Assignment', + "form": form, + "title": "Create New Assignment", + "button_text": "Create Assignment", } - return render(request, 'recruitment/agency_assignment_form.html', context) + return render(request, "recruitment/agency_assignment_form.html", context) -@login_required +@staff_user_required def agency_assignment_detail(request, slug): """View details of a specific agency assignment""" assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency', 'job'), - slug=slug + AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug ) # Get candidates submitted by this agency for this job - candidates = Candidate.objects.filter( - hiring_agency=assignment.agency, - job=assignment.job - ).order_by('-created_at') + candidates = Application.objects.filter( + hiring_agency=assignment.agency, job=assignment.job + ).order_by("-created_at") # Get access link if exists - access_link = getattr(assignment, 'access_link', None) + access_link = getattr(assignment, "access_link", None) # Get messages for this assignment - total_candidates = candidates.count() max_candidates = assignment.max_candidates circumference = 326.73 # 2 * π * r where r=52 if max_candidates > 0: - progress_percentage = (total_candidates / max_candidates) + progress_percentage = total_candidates / max_candidates stroke_dashoffset = circumference - (circumference * progress_percentage) else: stroke_dashoffset = circumference context = { - 'assignment': assignment, - 'candidates': candidates, - 'access_link': access_link, - - 'total_candidates': candidates.count(), - 'stroke_dashoffset': stroke_dashoffset, + "assignment": assignment, + "candidates": candidates, + "access_link": access_link, + "total_candidates": candidates.count(), + "stroke_dashoffset": stroke_dashoffset, } - return render(request, 'recruitment/agency_assignment_detail.html', context) + return render(request, "recruitment/agency_assignment_detail.html", context) -@login_required +@staff_user_required def agency_assignment_update(request, slug): """Update an existing agency assignment""" assignment = get_object_or_404(AgencyJobAssignment, slug=slug) - if request.method == 'POST': + if request.method == "POST": form = AgencyJobAssignmentForm(request.POST, instance=assignment) if form.is_valid(): assignment = form.save() - messages.success(request, f'Assignment updated successfully!') - return redirect('agency_assignment_detail', slug=assignment.slug) + messages.success(request, f"Assignment updated successfully!") + return redirect("agency_assignment_detail", slug=assignment.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = AgencyJobAssignmentForm(instance=assignment) context = { - 'form': form, - 'assignment': assignment, - 'title': f'Edit Assignment: {assignment.agency.name} - {assignment.job.title}', - 'button_text': 'Update Assignment', + "form": form, + "assignment": assignment, + "title": f"Edit Assignment: {assignment.agency.name} - {assignment.job.title}", + "button_text": "Update Assignment", } - return render(request, 'recruitment/agency_assignment_form.html', context) + return render(request, "recruitment/agency_assignment_form.html", context) -@login_required +@staff_user_required def agency_access_link_create(request): """Create access link for agency assignment""" - if request.method == 'POST': + if request.method == "POST": form = AgencyAccessLinkForm(request.POST) if form.is_valid(): access_link = form.save() - messages.success(request, f'Access link created for {access_link.assignment.agency.name}!') - return redirect('agency_assignment_detail', slug=access_link.assignment.slug) + messages.success( + request, + f"Access link created for {access_link.assignment.agency.name}!", + ) + return redirect( + "agency_assignment_detail", slug=access_link.assignment.slug + ) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = AgencyAccessLinkForm() context = { - 'form': form, - 'title': 'Create Access Link', - 'button_text': 'Create Link', + "form": form, + "title": "Create Access Link", + "button_text": "Create Link", } - return render(request, 'recruitment/agency_access_link_form.html', context) + return render(request, "recruitment/agency_access_link_form.html", context) -@login_required +@staff_user_required def agency_access_link_detail(request, slug): """View details of an access link""" access_link = get_object_or_404( - AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'), - slug=slug + AgencyAccessLink.objects.select_related( + "assignment__agency", "assignment__job" + ), + slug=slug, ) - context = { - 'access_link': access_link, + "access_link": access_link, } - return render(request, 'recruitment/agency_access_link_detail.html', context) + return render(request, "recruitment/agency_access_link_detail.html", context) - - - - - - - - - - - - - - -@login_required +@staff_user_required def agency_assignment_extend_deadline(request, slug): """Extend deadline for an agency assignment""" assignment = get_object_or_404(AgencyJobAssignment, slug=slug) - if request.method == 'POST': - new_deadline = request.POST.get('new_deadline') + if request.method == "POST": + new_deadline = request.POST.get("new_deadline") if new_deadline: try: from datetime import datetime - new_deadline_dt = datetime.fromisoformat(new_deadline.replace('Z', '+00:00')) + + new_deadline_dt = datetime.fromisoformat( + new_deadline.replace("Z", "+00:00") + ) # Ensure the new deadline is timezone-aware if timezone.is_naive(new_deadline_dt): new_deadline_dt = timezone.make_aware(new_deadline_dt) if assignment.extend_deadline(new_deadline_dt): - messages.success(request, f'Deadline extended to {new_deadline_dt.strftime("%Y-%m-%d %H:%M")}!') + messages.success( + request, + f"Deadline extended to {new_deadline_dt.strftime('%Y-%m-%d %H:%M')}!", + ) else: - messages.error(request, 'New deadline must be later than current deadline.') + messages.error( + request, "New deadline must be later than current deadline." + ) except ValueError: - messages.error(request, 'Invalid date format.') + messages.error(request, "Invalid date format.") else: - messages.error(request, 'Please provide a new deadline.') + messages.error(request, "Please provide a new deadline.") - return redirect('agency_assignment_detail', slug=assignment.slug) + return redirect("agency_assignment_detail", slug=assignment.slug) # Agency Portal Views (for external agencies) +@agency_user_required def agency_portal_login(request): """Agency login page""" - if request.session.get('agency_assignment_id'): - return redirect('agency_portal_dashboard') - if request.method == 'POST': + # if request.session.get("agency_assignment_id"): + # return redirect("agency_portal_dashboard") + if request.method == "POST": form = AgencyLoginForm(request.POST) if form.is_valid(): # Check if validated_access_link attribute exists - if hasattr(form, 'validated_access_link'): - access_link = form.validated_access_link - access_link.record_access() + # if hasattr(form, "validated_access_link"): + # access_link = form.validated_access_link + # access_link.record_access() # Store assignment in session - request.session['agency_assignment_id'] = access_link.assignment.id - request.session['agency_name'] = access_link.assignment.agency.name + # request.session["agency_assignment_id"] = access_link.assignment.id + # request.session["agency_name"] = access_link.assignment.agency.name - messages.success(request, f'Welcome, {access_link.assignment.agency.name}!') - return redirect('agency_portal_dashboard') + messages.success(request, f"Welcome, {access_link.assignment.agency.name}!") + return redirect("agency_portal_dashboard") else: - messages.error(request, 'Invalid token or password.') + messages.error(request, "Invalid token or password.") else: form = AgencyLoginForm() context = { - 'form': form, + "form": form, } - return render(request, 'recruitment/agency_portal_login.html', context) + return render(request, "recruitment/agency_portal_login.html", context) +def portal_login(request): + """Unified portal login for agency and candidate""" + if request.user.is_authenticated: + if request.user.user_type == "agency": + return redirect("agency_portal_dashboard") + if request.user.user_type == "candidate": + return redirect("candidate_portal_dashboard") + + if request.method == "POST": + form = PortalLoginForm(request.POST) + + if form.is_valid(): + email = form.cleaned_data["email"] + password = form.cleaned_data["password"] + user_type = form.cleaned_data["user_type"] + + # Authenticate user + user = authenticate(request, username=email, password=password) + if user is not None: + # Check if user type matches + print(user.user_type) + if hasattr(user, "user_type") and user.user_type == user_type: + login(request, user) + return redirect("agency_portal_dashboard") + + # if user_type == "agency": + # # Check if user has agency profile + # if hasattr(user, "agency_profile") and user.agency_profile: + # messages.success( + # request, f"Welcome, {user.agency_profile.name}!" + # ) + # return redirect("agency_portal_dashboard") + # else: + # messages.error( + # request, "No agency profile found for this user." + # ) + # logout(request) + + # elif user_type == "candidate": + # # Check if user has candidate profile + # if ( + # hasattr(user, "candidate_profile") + # and user.candidate_profile + # ): + # messages.success( + # request, + # f"Welcome, {user.candidate_profile.first_name}!", + # ) + # return redirect("candidate_portal_dashboard") + # else: + # messages.error( + # request, "No candidate profile found for this user." + # ) + # logout(request) + else: + messages.error(request, "Invalid user type selected.") + else: + messages.error(request, "Invalid email or password.") + else: + messages.error(request, "Please correct the errors below.") + else: + form = PortalLoginForm() + + context = { + "form": form, + } + return render(request, "recruitment/portal_login.html", context) + + +@candidate_user_required +def candidate_portal_dashboard(request): + """Candidate portal dashboard""" + if not request.user.is_authenticated: + return redirect("portal_login") + + # Get candidate profile + try: + candidate = request.user.candidate_profile + except: + messages.error(request, "No candidate profile found.") + return redirect("portal_login") + + context = { + "candidate": candidate, + } + return render(request, "recruitment/candidate_portal_dashboard.html", context) + + +@agency_user_required +def agency_portal_persons_list(request): + """Agency portal page showing all persons who come through this agency""" + try: + agency = request.user.agency_profile + except Exception as e: + print(e) + messages.error(request, "No agency profile found.") + return redirect("portal_login") + + # Get all applications for this agency + persons = Person.objects.filter(agency=agency) + # persons = Application.objects.filter( + # hiring_agency=agency + # ).select_related("job").order_by("-created_at") + + # Search functionality + search_query = request.GET.get("q", "") + if search_query: + persons = persons.filter( + Q(first_name__icontains=search_query) | + Q(last_name__icontains=search_query) | + Q(email__icontains=search_query) | + Q(phone__icontains=search_query) | + Q(job__title__icontains=search_query) + ) + + # Filter by stage if provided + stage_filter = request.GET.get("stage", "") + if stage_filter: + persons = persons.filter(stage=stage_filter) + + # Pagination + paginator = Paginator(persons, 20) # Show 20 persons per page + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + # Get stage choices for filter dropdown + stage_choices = Application.Stage.choices + person_form = PersonForm() + person_form.initial['agency'] = agency + + context = { + "agency": agency, + "page_obj": page_obj, + "search_query": search_query, + "stage_filter": stage_filter, + "stage_choices": stage_choices, + "total_persons": persons.count(), + "person_form": person_form, + } + return render(request, "recruitment/agency_portal_persons_list.html", context) + + +@agency_user_required def agency_portal_dashboard(request): """Agency portal dashboard showing all assignments for the agency""" - assignment_id = request.session.get('agency_assignment_id') - if not assignment_id: - return redirect('agency_portal_login') - # Get the current assignment to determine the agency - current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id - ) - - agency = current_assignment.agency + try: + agency = request.user.agency_profile + except Exception as e: + print(e) + messages.error(request, "No agency profile found.") + return redirect("portal_login") # Get ALL assignments for this agency - assignments = AgencyJobAssignment.objects.filter( - agency=agency - ).select_related('job').order_by('-created_at') + assignments = ( + AgencyJobAssignment.objects.filter(agency=agency) + .select_related("job") + .order_by("-created_at") + ) + current_assignment = assignments.filter(is_active=True).first() # Calculate statistics for each assignment assignment_stats = [] for assignment in assignments: - candidates = Candidate.objects.filter( - hiring_agency=agency, - job=assignment.job - ).order_by('-created_at') + candidates = Application.objects.filter( + hiring_agency=agency, job=assignment.job + ).order_by("-created_at") unread_messages = 0 - assignment_stats.append({ - 'assignment': assignment, - 'candidates': candidates, - 'candidate_count': candidates.count(), - 'unread_messages': unread_messages, - 'days_remaining': assignment.days_remaining, - 'is_active': assignment.is_currently_active, - 'can_submit': assignment.can_submit, - }) + assignment_stats.append( + { + "assignment": assignment, + "candidates": candidates, + "candidate_count": candidates.count(), + "unread_messages": unread_messages, + "days_remaining": assignment.days_remaining, + "is_active": assignment.is_currently_active, + "can_submit": assignment.can_submit, + } + ) # Get overall statistics - total_candidates = sum(stats['candidate_count'] for stats in assignment_stats) - total_unread_messages = sum(stats['unread_messages'] for stats in assignment_stats) - active_assignments = sum(1 for stats in assignment_stats if stats['is_active']) + total_candidates = sum(stats["candidate_count"] for stats in assignment_stats) + total_unread_messages = sum(stats["unread_messages"] for stats in assignment_stats) + active_assignments = sum(1 for stats in assignment_stats if stats["is_active"]) context = { - 'agency': agency, - 'current_assignment': current_assignment, - 'assignment_stats': assignment_stats, - 'total_assignments': assignments.count(), - 'active_assignments': active_assignments, - 'total_candidates': total_candidates, - 'total_unread_messages': total_unread_messages, + "agency": agency, + "current_assignment": current_assignment, + "assignment_stats": assignment_stats, + "total_assignments": assignments.count(), + "active_assignments": active_assignments, + "total_candidates": total_candidates, + "total_unread_messages": total_unread_messages, } - return render(request, 'recruitment/agency_portal_dashboard.html', context) + return render(request, "recruitment/agency_portal_dashboard.html", context) +@agency_user_required def agency_portal_submit_candidate_page(request, slug): """Dedicated page for submitting a candidate""" - assignment_id = request.session.get('agency_assignment_id') - if not assignment_id: - return redirect('agency_portal_login') + # assignment_id = request.session.get("agency_assignment_id") + # if not assignment_id: + # return redirect("agency_portal_login") # Get the specific assignment by slug and verify it belongs to the same agency - current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id - ) - + # current_assignment = get_object_or_404( + # AgencyJobAssignment.objects.select_related("agency"), slug=slug + # ) assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency', 'job'), - slug=slug + AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug ) - - if assignment.is_full: - messages.error(request, 'Maximum candidate limit reached for this assignment.') - return redirect('agency_portal_assignment_detail', slug=assignment.slug) + messages.error(request, "Maximum candidate limit reached for this assignment.") + return redirect("agency_portal_assignment_detail", slug=assignment.slug) # Verify this assignment belongs to the same agency as the logged-in session - if assignment.agency.id != current_assignment.agency.id: - messages.error(request, 'Access denied: This assignment does not belong to your agency.') - return redirect('agency_portal_dashboard') + if assignment.agency.id != assignment.agency.id: + messages.error( + request, "Access denied: This assignment does not belong to your agency." + ) + return redirect("agency_portal_dashboard") # Check if assignment allows submission if not assignment.can_submit: - messages.error(request, 'Cannot submit candidates: Assignment is not active, expired, or full.') - return redirect('agency_portal_assignment_detail', slug=assignment.slug) + messages.error( + request, + "Cannot submit candidates: Assignment is not active, expired, or full.", + ) + return redirect("agency_portal_assignment_detail", slug=assignment.slug) # Get total submitted candidates for this assignment - total_submitted = Candidate.objects.filter( - hiring_agency=assignment.agency, - job=assignment.job + total_submitted = Application.objects.filter( + hiring_agency=assignment.agency, job=assignment.job ).count() - if request.method == 'POST': - form = AgencyCandidateSubmissionForm(assignment, request.POST, request.FILES) + if request.method == "POST": + form = AgencyApplicationSubmissionForm(assignment, request.POST, request.FILES) if form.is_valid(): candidate = form.save(commit=False) - candidate.hiring_source = 'AGENCY' + candidate.hiring_source = "AGENCY" candidate.hiring_agency = assignment.agency candidate.save() assignment.increment_submission_count() # Handle AJAX requests - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return JsonResponse({ - 'success': True, - 'message': f'Candidate {candidate.name} submitted successfully!', - 'candidate_id': candidate.id - }) + if request.headers.get("X-Requested-With") == "XMLHttpRequest": + return JsonResponse( + { + "success": True, + "message": f"Candidate {candidate.name} submitted successfully!", + "candidate_id": candidate.id, + } + ) else: - messages.success(request, f'Candidate {candidate.name} submitted successfully!') - return redirect('agency_portal_assignment_detail', slug=assignment.slug) + messages.success( + request, f"Candidate {candidate.name} submitted successfully!" + ) + return redirect("agency_portal_assignment_detail", slug=assignment.slug) else: # Handle form validation errors for AJAX - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + if request.headers.get("X-Requested-With") == "XMLHttpRequest": error_messages = [] for field, errors in form.errors.items(): for error in errors: - error_messages.append(f'{field}: {error}') - return JsonResponse({ - 'success': False, - 'message': 'Please correct the following errors: ' + '; '.join(error_messages) - }) + error_messages.append(f"{field}: {error}") + return JsonResponse( + { + "success": False, + "message": "Please correct the following errors: " + + "; ".join(error_messages), + } + ) else: - messages.error(request, 'Please correct errors below.') + messages.error(request, "Please correct errors below.") else: - form = AgencyCandidateSubmissionForm(assignment) + form = AgencyApplicationSubmissionForm(assignment) context = { 'form': form, @@ -3502,100 +4106,112 @@ def agency_portal_submit_candidate_page(request, slug): 'total_submitted': total_submitted, 'job':assignment.job } - return render(request, 'recruitment/agency_portal_submit_candidate.html', context) + return render(request, "recruitment/agency_portal_submit_candidate.html", context) +@agency_user_required def agency_portal_submit_candidate(request): """Handle candidate submission via AJAX (for embedded form)""" - assignment_id = request.session.get('agency_assignment_id') + assignment_id = request.session.get("agency_assignment_id") if not assignment_id: - return redirect('agency_portal_login') + return redirect("agency_portal_login") assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency', 'job'), - id=assignment_id + AgencyJobAssignment.objects.select_related("agency", "job"), id=assignment_id ) if assignment.is_full: - messages.error(request, 'Maximum candidate limit reached for this assignment.') - return redirect('agency_portal_assignment_detail', slug=assignment.slug) + messages.error(request, "Maximum candidate limit reached for this assignment.") + return redirect("agency_portal_assignment_detail", slug=assignment.slug) # Check if assignment allows submission if not assignment.can_submit: - messages.error(request, 'Cannot submit candidates: Assignment is not active, expired, or full.') - return redirect('agency_portal_dashboard') + messages.error( + request, + "Cannot submit candidates: Assignment is not active, expired, or full.", + ) + return redirect("agency_portal_dashboard") - if request.method == 'POST': - form = AgencyCandidateSubmissionForm(assignment, request.POST, request.FILES) + if request.method == "POST": + form = AgencyApplicationSubmissionForm(assignment, request.POST, request.FILES) if form.is_valid(): candidate = form.save(commit=False) - candidate.hiring_source = 'AGENCY' + candidate.hiring_source = "AGENCY" candidate.hiring_agency = assignment.agency candidate.save() # Increment the assignment's submitted count assignment.increment_submission_count() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return JsonResponse({'success': True, 'message': f'Candidate {candidate.name} submitted successfully!'}) + if request.headers.get("X-Requested-With") == "XMLHttpRequest": + return JsonResponse( + { + "success": True, + "message": f"Candidate {candidate.name} submitted successfully!", + } + ) else: - messages.success(request, f'Candidate {candidate.name} submitted successfully!') - return redirect('agency_portal_dashboard') + messages.success( + request, f"Candidate {candidate.name} submitted successfully!" + ) + return redirect("agency_portal_dashboard") else: - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return JsonResponse({'success': False, 'message': 'Please correct the errors below.'}) + if request.headers.get("X-Requested-With") == "XMLHttpRequest": + return JsonResponse( + {"success": False, "message": "Please correct the errors below."} + ) else: - messages.error(request, 'Please correct errors below.') + messages.error(request, "Please correct errors below.") else: - form = AgencyCandidateSubmissionForm(assignment) + form = AgencyApplicationSubmissionForm(assignment) context = { - 'form': form, - 'assignment': assignment, - 'title': f'Submit Candidate for {assignment.job.title}', - 'button_text': 'Submit Candidate', + "form": form, + "assignment": assignment, + "title": f"Submit Candidate for {assignment.job.title}", + "button_text": "Submit Candidate", } - return render(request, 'recruitment/agency_portal_submit_candidate.html', context) - - + return render(request, "recruitment/agency_portal_submit_candidate.html", context) def agency_portal_assignment_detail(request, slug): """View details of a specific assignment - routes to admin or agency template""" - print(slug) # Check if this is an agency portal user (via session) - assignment_id = request.session.get('agency_assignment_id') - is_agency_user = bool(assignment_id) - return agency_assignment_detail_agency(request, slug, assignment_id) + # assignment_id = request.session.get("agency_assignment_id") + # is_agency_user = bool(assignment_id) + # return agency_assignment_detail_agency(request, slug, assignment_id) # if is_agency_user: # # Agency Portal User - Route to agency-specific template # else: # # Admin User - Route to admin template # return agency_assignment_detail_admin(request, slug) + assignment = get_object_or_404( + AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug + ) +@agency_user_required def agency_assignment_detail_agency(request, slug, assignment_id): """Handle agency portal assignment detail view""" # Get the assignment by slug and verify it belongs to same agency assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency', 'job'), - slug=slug + AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug ) # Verify this assignment belongs to the same agency as the logged-in session current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id + AgencyJobAssignment.objects.select_related("agency"), id=assignment_id ) if assignment.agency.id != current_assignment.agency.id: - messages.error(request, 'Access denied: This assignment does not belong to your agency.') - return redirect('agency_portal_dashboard') + messages.error( + request, "Access denied: This assignment does not belong to your agency." + ) + return redirect("agency_portal_dashboard") # Get candidates submitted by this agency for this job - candidates = Candidate.objects.filter( - hiring_agency=assignment.agency, - job=assignment.job - ).order_by('-created_at') + candidates = Application.objects.filter( + hiring_agency=assignment.agency, job=assignment.job + ).order_by("-created_at") # Get messages for this assignment messages = [] @@ -3605,12 +4221,12 @@ def agency_assignment_detail_agency(request, slug, assignment_id): # Pagination for candidates paginator = Paginator(candidates, 20) # Show 20 candidates per page - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) # Pagination for messages message_paginator = Paginator(messages, 15) # Show 15 messages per page - message_page_number = request.GET.get('message_page') + message_page_number = request.GET.get("message_page") message_page_obj = message_paginator.get_page(message_page_number) # Calculate progress ring offset for circular progress indicator @@ -3619,258 +4235,568 @@ def agency_assignment_detail_agency(request, slug, assignment_id): circumference = 326.73 # 2 * π * r where r=52 if max_candidates > 0: - progress_percentage = (total_candidates / max_candidates) + progress_percentage = total_candidates / max_candidates stroke_dashoffset = circumference - (circumference * progress_percentage) else: stroke_dashoffset = circumference context = { - 'assignment': assignment, - 'page_obj': page_obj, - 'message_page_obj': message_page_obj, - 'total_candidates': total_candidates, - 'stroke_dashoffset': stroke_dashoffset, + "assignment": assignment, + "page_obj": page_obj, + "message_page_obj": message_page_obj, + "total_candidates": total_candidates, + "stroke_dashoffset": stroke_dashoffset, } - return render(request, 'recruitment/agency_portal_assignment_detail.html', context) + return render(request, "recruitment/agency_portal_assignment_detail.html", context) +@staff_user_required def agency_assignment_detail_admin(request, slug): """Handle admin assignment detail view""" assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency', 'job'), - slug=slug + AgencyJobAssignment.objects.select_related("agency", "job"), slug=slug ) # Get candidates submitted by this agency for this job - candidates = Candidate.objects.filter( - hiring_agency=assignment.agency, - job=assignment.job - ).order_by('-created_at') + candidates = Application.objects.filter( + hiring_agency=assignment.agency, job=assignment.job + ).order_by("-created_at") # Get access link if exists - access_link = getattr(assignment, 'access_link', None) + access_link = getattr(assignment, "access_link", None) # Get messages for this assignment messages = [] context = { - 'assignment': assignment, - 'candidates': candidates, - 'access_link': access_link, - 'total_candidates': candidates.count(), + "assignment": assignment, + "candidates": candidates, + "access_link": access_link, + "total_candidates": candidates.count(), } - return render(request, 'recruitment/agency_assignment_detail.html', context) + return render(request, "recruitment/agency_assignment_detail.html", context) +@agency_user_required def agency_portal_edit_candidate(request, candidate_id): """Edit a candidate for agency portal""" - assignment_id = request.session.get('agency_assignment_id') + assignment_id = request.session.get("agency_assignment_id") if not assignment_id: - return redirect('agency_portal_login') + return redirect("agency_portal_login") # Get current assignment to determine agency current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id + AgencyJobAssignment.objects.select_related("agency"), id=assignment_id ) agency = current_assignment.agency # Get candidate and verify it belongs to this agency - candidate = get_object_or_404(Candidate, id=candidate_id, hiring_agency=agency) + candidate = get_object_or_404(Application, id=candidate_id, hiring_agency=agency) - if request.method == 'POST': + if request.method == "POST": # Handle form submission - candidate.first_name = request.POST.get('first_name', candidate.first_name) - candidate.last_name = request.POST.get('last_name', candidate.last_name) - candidate.email = request.POST.get('email', candidate.email) - candidate.phone = request.POST.get('phone', candidate.phone) - candidate.address = request.POST.get('address', candidate.address) + candidate.first_name = request.POST.get("first_name", candidate.first_name) + candidate.last_name = request.POST.get("last_name", candidate.last_name) + candidate.email = request.POST.get("email", candidate.email) + candidate.phone = request.POST.get("phone", candidate.phone) + candidate.address = request.POST.get("address", candidate.address) # Handle resume upload if provided - if 'resume' in request.FILES: - candidate.resume = request.FILES['resume'] + if "resume" in request.FILES: + candidate.resume = request.FILES["resume"] try: candidate.save() - messages.success(request, f'Candidate {candidate.name} updated successfully!') - return redirect('agency_assignment_detail', slug=candidate.job.agencyjobassignment_set.first().slug) + messages.success( + request, f"Candidate {candidate.name} updated successfully!" + ) + return redirect( + "agency_assignment_detail", + slug=candidate.job.agencyjobassignment_set.first().slug, + ) except Exception as e: - messages.error(request, f'Error updating candidate: {e}') + messages.error(request, f"Error updating candidate: {e}") # For GET requests or POST errors, return JSON response for AJAX - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return JsonResponse({ - 'success': True, - 'candidate': { - 'id': candidate.id, - 'first_name': candidate.first_name, - 'last_name': candidate.last_name, - 'email': candidate.email, - 'phone': candidate.phone, - 'address': candidate.address, + if request.headers.get("X-Requested-With") == "XMLHttpRequest": + return JsonResponse( + { + "success": True, + "candidate": { + "id": candidate.id, + "first_name": candidate.first_name, + "last_name": candidate.last_name, + "email": candidate.email, + "phone": candidate.phone, + "address": candidate.address, + }, } - }) + ) # Fallback for non-AJAX requests - return redirect('agency_portal_dashboard') + return redirect("agency_portal_dashboard") +@agency_user_required def agency_portal_delete_candidate(request, candidate_id): """Delete a candidate for agency portal""" - assignment_id = request.session.get('agency_assignment_id') + assignment_id = request.session.get("agency_assignment_id") if not assignment_id: - return redirect('agency_portal_login') + return redirect("agency_portal_login") # Get current assignment to determine agency current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id + AgencyJobAssignment.objects.select_related("agency"), id=assignment_id ) agency = current_assignment.agency # Get candidate and verify it belongs to this agency - candidate = get_object_or_404(Candidate, id=candidate_id, hiring_agency=agency) + candidate = get_object_or_404(Application, id=candidate_id, hiring_agency=agency) - if request.method == 'POST': + if request.method == "POST": try: candidate_name = candidate.name candidate.delete() current_assignment.candidates_submitted -= 1 current_assignment.status = current_assignment.AssignmentStatus.ACTIVE - current_assignment.save(update_fields=['candidates_submitted','status']) + current_assignment.save(update_fields=["candidates_submitted", "status"]) - messages.success(request, f'Candidate {candidate_name} removed successfully!') - return JsonResponse({'success': True}) + messages.success( + request, f"Candidate {candidate_name} removed successfully!" + ) + return JsonResponse({"success": True}) except Exception as e: - return JsonResponse({'success': False, 'error': str(e)}) + return JsonResponse({"success": False, "error": str(e)}) # For GET requests, return error + return JsonResponse({"success": False, "error": "Method not allowed"}) + + +# Message Views +@staff_user_required +def message_list(request): + """List all messages for the current user""" + # Get filter parameters + status_filter = request.GET.get("status", "") + message_type_filter = request.GET.get("type", "") + search_query = request.GET.get("q", "") + + # Base queryset - get messages where user is either sender or recipient + message_list = Message.objects.filter( + Q(sender=request.user) | Q(recipient=request.user) + ).select_related("sender", "recipient", "job").order_by("-created_at") + + # Apply filters + if status_filter: + if status_filter == "read": + message_list = message_list.filter(is_read=True) + elif status_filter == "unread": + message_list = message_list.filter(is_read=False) + + if message_type_filter: + message_list = message_list.filter(message_type=message_type_filter) + + if search_query: + message_list = message_list.filter( + Q(subject__icontains=search_query) | + Q(content__icontains=search_query) + ) + + # Pagination + paginator = Paginator(message_list, 20) # Show 20 messages per page + page_number = request.GET.get("page") + page_obj = paginator.get_page(page_number) + + # Statistics + total_messages = message_list.count() + unread_messages = message_list.filter(is_read=False).count() + + context = { + "page_obj": page_obj, + "total_messages": total_messages, + "unread_messages": unread_messages, + "status_filter": status_filter, + "type_filter": message_type_filter, + "search_query": search_query, + } + return render(request, "messages/message_list.html", context) + + +@login_required +def message_detail(request, message_id): + """View details of a specific message""" + message = get_object_or_404( + Message.objects.select_related("sender", "recipient", "job"), + id=message_id + ) + + # Check if user has permission to view this message + if message.sender != request.user and message.recipient != request.user: + messages.error(request, "You don't have permission to view this message.") + return redirect("message_list") + + # Mark as read if it was unread and user is the recipient + if not message.is_read and message.recipient == request.user: + message.is_read = True + message.read_at = timezone.now() + message.save(update_fields=["is_read", "read_at"]) + + context = { + "message": message, + } + return render(request, "messages/message_detail.html", context) + + +@login_required +def message_create(request): + """Create a new message""" + if request.method == "POST": + form = MessageForm(request.user, request.POST) + if form.is_valid(): + message = form.save(commit=False) + message.sender = request.user + message.save() + + messages.success(request, "Message sent successfully!") + return redirect("message_list") + else: + messages.error(request, "Please correct the errors below.") + else: + form = MessageForm(request.user) + + context = { + "form": form, + } + return render(request, "messages/message_form.html", context) + + +@login_required +def message_reply(request, message_id): + """Reply to a message""" + parent_message = get_object_or_404( + Message.objects.select_related("sender", "recipient", "job"), + id=message_id + ) + + # Check if user has permission to reply to this message + if parent_message.recipient != request.user and parent_message.sender != request.user: + messages.error(request, "You don't have permission to reply to this message.") + return redirect("message_list") + + if request.method == "POST": + form = MessageForm(request.user, request.POST) + if form.is_valid(): + message = form.save(commit=False) + message.sender = request.user + message.parent_message = parent_message + # Set recipient as the original sender + message.recipient = parent_message.sender + message.save() + + messages.success(request, "Reply sent successfully!") + return redirect("message_detail", message_id=parent_message.id) + else: + messages.error(request, "Please correct the errors below.") + else: + # Pre-fill form with reply context + form = MessageForm(request.user) + form.initial["subject"] = f"Re: {parent_message.subject}" + form.initial["recipient"] = parent_message.sender + if parent_message.job: + form.initial["job"] = parent_message.job + form.initial["message_type"] = Message.MessageType.JOB_RELATED + + context = { + "form": form, + "parent_message": parent_message, + } + return render(request, "messages/message_form.html", context) + + +@login_required +def message_mark_read(request, message_id): + """Mark a message as read""" + message = get_object_or_404( + Message.objects.select_related("sender", "recipient"), + id=message_id + ) + + # Check if user has permission to mark this message as read + if message.recipient != request.user: + messages.error(request, "You can only mark messages you received as read.") + return redirect("message_list") + + # Mark as read + message.is_read = True + message.read_at = timezone.now() + message.save(update_fields=["is_read", "read_at"]) + + messages.success(request, "Message marked as read.") + + # Handle HTMX requests + if "HX-Request" in request.headers: + return HttpResponse(status=200) # HTMX success response + + return redirect("message_list") + + +@login_required +def message_mark_unread(request, message_id): + """Mark a message as unread""" + message = get_object_or_404( + Message.objects.select_related("sender", "recipient"), + id=message_id + ) + + # Check if user has permission to mark this message as unread + if message.recipient != request.user: + messages.error(request, "You can only mark messages you received as unread.") + return redirect("message_list") + + # Mark as unread + message.is_read = False + message.read_at = None + message.save(update_fields=["is_read", "read_at"]) + + messages.success(request, "Message marked as unread.") + + # Handle HTMX requests + if "HX-Request" in request.headers: + return HttpResponse(status=200) # HTMX success response + + return redirect("message_list") + + +@login_required +def message_delete(request, message_id): + """Delete a message""" + message = get_object_or_404( + Message.objects.select_related("sender", "recipient"), + id=message_id + ) + + # Check if user has permission to delete this message + if message.sender != request.user and message.recipient != request.user: + messages.error(request, "You don't have permission to delete this message.") + return redirect("message_list") + + if request.method == "POST": + message.delete() + messages.success(request, "Message deleted successfully.") + + # Handle HTMX requests + if "HX-Request" in request.headers: + return HttpResponse(status=200) # HTMX success response + + return redirect("message_list") + + # For GET requests, show confirmation page + context = { + "message": message, + "title": "Delete Message", + "message": f'Are you sure you want to delete this message from {message.sender.get_full_name() or message.sender.username}?', + "cancel_url": reverse("message_detail", kwargs={"message_id": message_id}), + } + return render(request, "messages/message_confirm_delete.html", context) + + +@login_required +def api_unread_count(request): + """API endpoint to get unread message count""" + unread_count = Message.objects.filter( + recipient=request.user, + is_read=False + ).count() + + return JsonResponse({"unread_count": unread_count}) + + +# Document Views +@login_required +def document_upload(request, application_id): + """Upload a document for an application""" + application = get_object_or_404(Application, pk=application_id) + + if request.method == "POST": + if request.FILES.get('file'): + document = Document.objects.create( + content_object=application, # Use Generic Foreign Key to link to Application + file=request.FILES['file'], + document_type=request.POST.get('document_type', 'other'), + description=request.POST.get('description', ''), + uploaded_by=request.user, + ) + messages.success(request, f'Document "{document.get_document_type_display()}" uploaded successfully!') + return redirect('candidate_detail', slug=application.job.slug) + + +@login_required +def document_delete(request, document_id): + """Delete a document""" + document = get_object_or_404(Document, id=document_id) + + # Check permission - document is now linked to Application via Generic Foreign Key + if hasattr(document.content_object, 'job'): + if document.content_object.job.assigned_to != request.user and not request.user.is_superuser: + messages.error(request, "You don't have permission to delete this document.") + return JsonResponse({'success': False, 'error': 'Permission denied'}) + job_slug = document.content_object.job.slug + else: + # Handle other content object types + messages.error(request, "You don't have permission to delete this document.") + return JsonResponse({'success': False, 'error': 'Permission denied'}) + + if request.method == "POST": + file_name = document.file.name if document.file else "Unknown" + document.delete() + messages.success(request, f'Document "{file_name}" deleted successfully!') + + # Handle AJAX requests + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return JsonResponse({'success': True, 'message': 'Document deleted successfully!'}) + else: + return redirect('candidate_detail', slug=job_slug) + return JsonResponse({'success': False, 'error': 'Method not allowed'}) -def agency_portal_logout(request): - """Logout from agency portal""" - if 'agency_assignment_id' in request.session: - del request.session['agency_assignment_id'] - if 'agency_name' in request.session: - del request.session['agency_name'] +@login_required +def document_download(request, document_id): + """Download a document""" + document = get_object_or_404(Document, id=document_id) - messages.success(request, 'You have been logged out.') - return redirect('agency_portal_login') + # Check permission - document is now linked to Application via Generic Foreign Key + if hasattr(document.content_object, 'job'): + if document.content_object.job.assigned_to != request.user and not request.user.is_superuser: + messages.error(request, "You don't have permission to download this document.") + return JsonResponse({'success': False, 'error': 'Permission denied'}) + else: + # Handle other content object types + messages.error(request, "You don't have permission to download this document.") + return JsonResponse({'success': False, 'error': 'Permission denied'}) + + if document.file: + response = HttpResponse(document.file.read(), content_type='application/octet-stream') + response['Content-Disposition'] = f'attachment; filename="{document.file.name}"' + return response + + return JsonResponse({'success': False, 'error': 'File not found'}) + + +@login_required +def portal_logout(request): + """Logout from portal""" + logout(request) + + messages.success(request, "You have been logged out.") + return redirect("portal_login") @login_required def agency_access_link_deactivate(request, slug): """Deactivate an agency access link""" access_link = get_object_or_404( - AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'), - slug=slug + AgencyAccessLink.objects.select_related( + "assignment__agency", "assignment__job" + ), + slug=slug, ) - if request.method == 'POST': + if request.method == "POST": access_link.is_active = False - access_link.save(update_fields=['is_active']) + access_link.save(update_fields=["is_active"]) messages.success( request, - f'Access link for {access_link.assignment.agency.name} - {access_link.assignment.job.title} has been deactivated.' + f"Access link for {access_link.assignment.agency.name} - {access_link.assignment.job.title} has been deactivated.", ) # Handle HTMX requests - if 'HX-Request' in request.headers: + if "HX-Request" in request.headers: return HttpResponse(status=200) # HTMX success response - return redirect('agency_assignment_detail', slug=access_link.assignment.slug) + return redirect("agency_assignment_detail", slug=access_link.assignment.slug) # For GET requests, show confirmation page context = { - 'access_link': access_link, - 'title': 'Deactivate Access Link', - 'message': f'Are you sure you want to deactivate the access link for {access_link.assignment.agency.name}?', - 'cancel_url': reverse('agency_assignment_detail', kwargs={'slug': access_link.assignment.slug}), + "access_link": access_link, + "title": "Deactivate Access Link", + "message": f"Are you sure you want to deactivate the access link for {access_link.assignment.agency.name}?", + "cancel_url": reverse( + "agency_assignment_detail", kwargs={"slug": access_link.assignment.slug} + ), } - return render(request, 'recruitment/agency_access_link_confirm.html', context) + return render(request, "recruitment/agency_access_link_confirm.html", context) @login_required def agency_access_link_reactivate(request, slug): """Reactivate an agency access link""" access_link = get_object_or_404( - AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'), - slug=slug + AgencyAccessLink.objects.select_related( + "assignment__agency", "assignment__job" + ), + slug=slug, ) - if request.method == 'POST': + if request.method == "POST": access_link.is_active = True - access_link.save(update_fields=['is_active']) + access_link.save(update_fields=["is_active"]) messages.success( request, - f'Access link for {access_link.assignment.agency.name} - {access_link.assignment.job.title} has been reactivated.' + f"Access link for {access_link.assignment.agency.name} - {access_link.assignment.job.title} has been reactivated.", ) # Handle HTMX requests - if 'HX-Request' in request.headers: + if "HX-Request" in request.headers: return HttpResponse(status=200) # HTMX success response - return redirect('agency_assignment_detail', slug=access_link.assignment.slug) + return redirect("agency_assignment_detail", slug=access_link.assignment.slug) # For GET requests, show confirmation page context = { - 'access_link': access_link, - 'title': 'Reactivate Access Link', - 'message': f'Are you sure you want to reactivate the access link for {access_link.assignment.agency.name}?', - 'cancel_url': reverse('agency_assignment_detail', kwargs={'slug': access_link.assignment.slug}), + "access_link": access_link, + "title": "Reactivate Access Link", + "message": f"Are you sure you want to reactivate the access link for {access_link.assignment.agency.name}?", + "cancel_url": reverse( + "agency_assignment_detail", kwargs={"slug": access_link.assignment.slug} + ), } - return render(request, 'recruitment/agency_access_link_confirm.html', context) - - - - - - - - - - - - - - - + return render(request, "recruitment/agency_access_link_confirm.html", context) +@agency_user_required def api_candidate_detail(request, candidate_id): """API endpoint to get candidate details for agency portal""" try: # Get candidate from session-based agency access - assignment_id = request.session.get('agency_assignment_id') + assignment_id = request.session.get("agency_assignment_id") if not assignment_id: - return JsonResponse({'success': False, 'error': 'Access denied'}) + return JsonResponse({"success": False, "error": "Access denied"}) # Get current assignment to determine agency current_assignment = get_object_or_404( - AgencyJobAssignment.objects.select_related('agency'), - id=assignment_id + AgencyJobAssignment.objects.select_related("agency"), id=assignment_id ) agency = current_assignment.agency # Get candidate and verify it belongs to this agency - candidate = get_object_or_404(Candidate, id=candidate_id, hiring_agency=agency) + candidate = get_object_or_404(Application, id=candidate_id, hiring_agency=agency) # Return candidate data response_data = { - 'success': True, - 'id': candidate.id, - 'first_name': candidate.first_name, - 'last_name': candidate.last_name, - 'email': candidate.email, - 'phone': candidate.phone, - 'address': candidate.address, + "success": True, + "id": candidate.id, + "first_name": candidate.first_name, + "last_name": candidate.last_name, + "email": candidate.email, + "phone": candidate.phone, + "address": candidate.address, } return JsonResponse(response_data) @@ -3879,38 +4805,80 @@ def api_candidate_detail(request, candidate_id): return JsonResponse({'success': False, 'error': str(e)}) - -@login_required -def compose_candidate_email(request, job_slug): +@staff_user_required +def compose_candidate_email(request, job_slug, candidate_slug): """Compose email to participants about a candidate""" from .email_service import send_bulk_email job = get_object_or_404(JobPosting, slug=job_slug) + candidate = get_object_or_404(Application, slug=candidate_slug, job=job) + if request.method == "POST": + form = CandidateEmailForm(job, candidate, request.POST) candidate_ids=request.GET.getlist('candidate_ids') - candidates=Candidate.objects.filter(id__in=candidate_ids) + candidates=Application.objects.filter(id__in=candidate_ids) if request.method == 'POST': print("........................................................inside candidate conpose.............") candidate_ids = request.POST.getlist('candidate_ids') - candidates=Candidate.objects.filter(id__in=candidate_ids) + candidates=Application.objects.filter(id__in=candidate_ids) form = CandidateEmailForm(job, candidates, request.POST) if form.is_valid(): print("form is valid ...") # Get email addresses email_addresses = form.get_email_addresses() + if not email_addresses: + messages.error( + request, "No valid email addresses found for selected recipients." + ) + return render( + request, + "includes/email_compose_form.html", + {"form": form, "job": job, "candidate": candidate}, + ) + + # Check if this is an interview invitation + subject = form.cleaned_data.get("subject", "").lower() + is_interview_invitation = "interview" in subject or "meeting" in subject + + if is_interview_invitation: + # Use HTML template for interview invitations + meeting_details = None + if form.cleaned_data.get("include_meeting_details"): + # Try to get meeting details from candidate + meeting_details = { + "topic": f"Interview for {job.title}", + "date_time": getattr( + candidate, "interview_date", "To be scheduled" + ), + "duration": "60 minutes", + "join_url": getattr(candidate, "meeting_url", ""), + } + + from .email_service import send_interview_invitation_email + + email_result = send_interview_invitation_email( + candidate=candidate, + job=job, + meeting_details=meeting_details, + recipient_list=email_addresses, + ) + else: + # Get formatted message for regular emails + message = form.get_formatted_message() + subject = form.cleaned_data.get("subject") print(email_addresses) - - + + if not email_addresses: messages.error(request, 'No email selected') referer = request.META.get('HTTP_REFERER') - + if referer: # Redirect back to the referring page return redirect(referer) else: - + return redirect('dashboard') @@ -3918,7 +4886,7 @@ def compose_candidate_email(request, job_slug): subject = form.cleaned_data.get('subject') # Send emails using email service (no attachments, synchronous to avoid pickle issues) - + email_result = send_bulk_email( subject=subject, message=message, @@ -3929,16 +4897,45 @@ def compose_candidate_email(request, job_slug): from_interview=False ) - if email_result['success']: - messages.success(request, f'Email sent successfully to {len(email_addresses)} recipient(s).') + if email_result["success"]: + messages.success( + request, + f"Email sent successfully to {len(email_addresses)} recipient(s).", + ) - + # For HTMX requests, return success response + if "HX-Request" in request.headers: + return JsonResponse( + { + "success": True, + "message": f"Email sent successfully to {len(email_addresses)} recipient(s).", + } + ) - return redirect('candidate_interview_view', slug=job.slug) + return redirect("candidate_interview_view", slug=job.slug) else: - messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}') + messages.error( + request, + f"Failed to send email: {email_result.get('message', 'Unknown error')}", + ) + + # For HTMX requests, return error response + if "HX-Request" in request.headers: + return JsonResponse( + { + "success": False, + "error": email_result.get( + "message", "Failed to send email" + ), + } + ) + + return render( + request, + "includes/email_compose_form.html", + {"form": form, "job": job, "candidate": candidate}, + ) - return render(request, 'includes/email_compose_form.html', { 'form': form, 'job': job, @@ -3946,160 +4943,175 @@ def compose_candidate_email(request, job_slug): }) # except Exception as e: - + # logger.error(f"Error sending candidate email: {e}") + # messages.error(request, f'An error occurred while sending the email: {str(e)}') + + # # For HTMX requests, return error response + # if 'HX-Request' in request.headers: + # return JsonResponse({ + # 'success': False, + # 'error': f'An error occurred while sending the email: {str(e)}' + # }) + + # return render(request, 'includes/email_compose_form.html', { + # 'form': form, + # 'job': job, + # 'candidate': candidate + # }) else: # Form validation errors print('form is not valid') print(form.errors) - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") # For HTMX requests, return error response - if 'HX-Request' in request.headers: - return JsonResponse({ - 'success': False, - 'error': 'Please correct the form errors and try again.' - }) - - return render(request, 'includes/email_compose_form.html', { - 'form': form, - 'job': job, - 'candidates': candidates - }) + if "HX-Request" in request.headers: + return JsonResponse( + { + "success": False, + "error": "Please correct the form errors and try again.", + } + ) - else: + return render( + request, + "includes/email_compose_form.html", + {"form": form, "job": job, "candidates": candidate},s + ) + else: # GET request - show the form - form = CandidateEmailForm(job, candidates) - - print("GET request made for candidate email form") + form = CandidateEmailForm(job, candidate) - return render(request, 'includes/email_compose_form.html', { - 'form': form, - 'job': job, - 'candidates':candidates - - - }) + return render( + request, + "includes/email_compose_form.html", + {"form": form, "job": job, "candidate": candidate}, + ) # Source CRUD Views -@login_required +@staff_user_required def source_list(request): """List all sources with search and pagination""" - search_query = request.GET.get('q', '') + search_query = request.GET.get("q", "") sources = Source.objects.all() if search_query: sources = sources.filter( - Q(name__icontains=search_query) | - Q(source_type__icontains=search_query) | - Q(description__icontains=search_query) + Q(name__icontains=search_query) + | Q(source_type__icontains=search_query) + | Q(description__icontains=search_query) ) # Order by most recently created - sources = sources.order_by('-created_at') + sources = sources.order_by("-created_at") # Pagination paginator = Paginator(sources, 15) # Show 15 sources per page - page_number = request.GET.get('page') + page_number = request.GET.get("page") page_obj = paginator.get_page(page_number) context = { - 'page_obj': page_obj, - 'search_query': search_query, - 'total_sources': sources.count(), + "page_obj": page_obj, + "search_query": search_query, + "total_sources": sources.count(), } - return render(request, 'recruitment/source_list.html', context) + return render(request, "recruitment/source_list.html", context) -@login_required +@staff_user_required def source_create(request): """Create a new source""" - if request.method == 'POST': + if request.method == "POST": form = SourceForm(request.POST) if form.is_valid(): source = form.save() messages.success(request, f'Source "{source.name}" created successfully!') - return redirect('source_detail', slug=source.slug) + return redirect("source_detail", slug=source.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = SourceForm() context = { - 'form': form, - 'title': 'Create New Source', - 'button_text': 'Create Source', + "form": form, + "title": "Create New Source", + "button_text": "Create Source", } - return render(request, 'recruitment/source_form.html', context) + return render(request, "recruitment/source_form.html", context) -@login_required +@staff_user_required def source_detail(request, slug): """View details of a specific source""" source = get_object_or_404(Source, slug=slug) # Get integration logs for this source - integration_logs = source.integration_logs.order_by('-created_at')[:10] # Show recent 10 logs + integration_logs = source.integration_logs.order_by("-created_at")[ + :10 + ] # Show recent 10 logs # Statistics total_logs = source.integration_logs.count() - successful_logs = source.integration_logs.filter(method='POST').count() - failed_logs = source.integration_logs.filter(method='POST', status_code__gte=400).count() + successful_logs = source.integration_logs.filter(method="POST").count() + failed_logs = source.integration_logs.filter( + method="POST", status_code__gte=400 + ).count() context = { - 'source': source, - 'integration_logs': integration_logs, - 'total_logs': total_logs, - 'successful_logs': successful_logs, - 'failed_logs': failed_logs, + "source": source, + "integration_logs": integration_logs, + "total_logs": total_logs, + "successful_logs": successful_logs, + "failed_logs": failed_logs, } - return render(request, 'recruitment/source_detail.html', context) + return render(request, "recruitment/source_detail.html", context) -@login_required +@staff_user_required def source_update(request, slug): """Update an existing source""" source = get_object_or_404(Source, slug=slug) - if request.method == 'POST': + if request.method == "POST": form = SourceForm(request.POST, instance=source) if form.is_valid(): source = form.save() messages.success(request, f'Source "{source.name}" updated successfully!') - return redirect('source_detail', slug=source.slug) + return redirect("source_detail", slug=source.slug) else: - messages.error(request, 'Please correct the errors below.') + messages.error(request, "Please correct the errors below.") else: form = SourceForm(instance=source) context = { - 'form': form, - 'source': source, - 'title': f'Edit Source: {source.name}', - 'button_text': 'Update Source', + "form": form, + "source": source, + "title": f"Edit Source: {source.name}", + "button_text": "Update Source", } - return render(request, 'recruitment/source_form.html', context) + return render(request, "recruitment/source_form.html", context) -@login_required +@staff_user_required def source_delete(request, slug): """Delete a source""" source = get_object_or_404(Source, slug=slug) - if request.method == 'POST': + if request.method == "POST": source_name = source.name source.delete() messages.success(request, f'Source "{source_name}" deleted successfully!') - return redirect('source_list') + return redirect("source_list") context = { - 'source': source, - 'title': 'Delete Source', - 'message': f'Are you sure you want to delete the source "{source.name}"?', - 'cancel_url': reverse('source_detail', kwargs={'slug': source.slug}), + "source": source, + "title": "Delete Source", + "message": f'Are you sure you want to delete the source "{source.name}"?', + "cancel_url": reverse("source_detail", kwargs={"slug": source.slug}), } - return render(request, 'recruitment/source_confirm_delete.html', context) + return render(request, "recruitment/source_confirm_delete.html", context) @login_required @@ -4107,24 +5119,25 @@ def source_generate_keys(request, slug): """Generate new API keys for a source""" source = get_object_or_404(Source, slug=slug) - if request.method == 'POST': + if request.method == "POST": # Generate new API key and secret from .forms import generate_api_key, generate_api_secret + source.api_key = generate_api_key() source.api_secret = generate_api_secret() - source.save(update_fields=['api_key', 'api_secret']) + source.save(update_fields=["api_key", "api_secret"]) messages.success(request, f'New API keys generated for "{source.name}"!') - return redirect('source_detail', slug=source.slug) + return redirect("source_detail", slug=source.slug) # For GET requests, show confirmation page context = { - 'source': source, - 'title': 'Generate New API Keys', - 'message': f'Are you sure you want to generate new API keys for "{source.name}"? This will invalidate the existing keys.', - 'cancel_url': reverse('source_detail', kwargs={'slug': source.slug}), + "source": source, + "title": "Generate New API Keys", + "message": f'Are you sure you want to generate new API keys for "{source.name}"? This will invalidate the existing keys.', + "cancel_url": reverse("source_detail", kwargs={"slug": source.slug}), } - return render(request, 'recruitment/source_confirm_generate_keys.html', context) + return render(request, "recruitment/source_confirm_generate_keys.html", context) @login_required @@ -4132,21 +5145,39 @@ def source_toggle_status(request, slug): """Toggle active status of a source""" source = get_object_or_404(Source, slug=slug) - if request.method == 'POST': + if request.method == "POST": source.is_active = not source.is_active - source.save(update_fields=['is_active']) + source.save(update_fields=["is_active"]) - status_text = 'activated' if source.is_active else 'deactivated' + status_text = "activated" if source.is_active else "deactivated" messages.success(request, f'Source "{source.name}" has been {status_text}!') # Handle HTMX requests - if 'HX-Request' in request.headers: + if "HX-Request" in request.headers: return HttpResponse(status=200) # HTMX success response - return redirect('source_detail', slug=source.slug) + return redirect("source_detail", slug=source.slug) # For GET requests, return error - return JsonResponse({'success': False, 'error': 'Method not allowed'}) + return JsonResponse({"success": False, "error": "Method not allowed"}) + + +def candidate_signup(request,slug): + from .forms import CandidateSignupForm + + job = get_object_or_404(JobPosting, slug=slug) + if request.method == "POST": + form = CandidateSignupForm(request.POST) + if form.is_valid(): + try: + application = form.save(job) + return redirect("application_success", slug=job.slug) + except Exception as e: + messages.error(request, f"Error creating application: {str(e)}") + return render(request, "recruitment/candidate_signup.html", {"form": form, "job": job}) + + form = CandidateSignupForm() + return render(request, "recruitment/candidate_signup.html", {"form": form, "job": job}) @@ -4162,20 +5193,20 @@ def create_interview_participants(request,slug): candidate = form.save(commit=False) candidate.save() # This is important for ManyToMany fields: save the many-to-many data - form.save_m2m() + form.save_m2m() return redirect('meeting_details',slug=interview_slug) # Redirect to a success page else: form = InterviewParticpantsForm(instance=schedule_interview) - + return render(request, 'interviews/interview_participants_form.html', {'form': form}) from django.core.mail import send_mail def send_interview_email(request, slug): from .email_service import send_bulk_email - + interview = get_object_or_404(ScheduledInterview, slug=slug) - + # 2. Retrieve the required data for the form's constructor candidate = interview.candidate job=interview.job @@ -4190,16 +5221,16 @@ def send_interview_email(request, slug): # --- POST REQUEST HANDLING --- if request.method == 'POST': - + form = InterviewEmailForm( - request.POST, + request.POST, candidate=candidate, external_participants=external_participants, system_participants=system_participants, - meeting=meeting, + meeting=meeting, job=job ) - + if form.is_valid(): # 4. Extract cleaned data subject = form.cleaned_data['subject'] @@ -4213,7 +5244,7 @@ def send_interview_email(request, slug): subject, msg_agency, settings.DEFAULT_FROM_EMAIL, - [candidate.hiring_agency.email], + [candidate.hiring_agency.email], fail_silently=False, ) else: @@ -4221,11 +5252,11 @@ def send_interview_email(request, slug): subject, msg_candidate, settings.DEFAULT_FROM_EMAIL, - [candidate.email], + [candidate.email], fail_silently=False, ) - + email_result = send_bulk_email( subject=subject, message=msg_participants, @@ -4235,7 +5266,7 @@ def send_interview_email(request, slug): async_task_=True, # Changed to False to avoid pickle issues, from_interview=True ) - + if email_result['success']: messages.success(request, f'Email sent successfully to {total_recipients} recipient(s).') @@ -4243,9 +5274,9 @@ def send_interview_email(request, slug): else: messages.error(request, f'Failed to send email: {email_result.get("message", "Unknown error")}') return redirect('list_meetings') - - + + # def schedule_interview_location_form(request,slug): # schedule=get_object_or_404(InterviewSchedule,slug=slug) # if request.method=='POST': @@ -4255,7 +5286,7 @@ def send_interview_email(request, slug): # else: # form=InterviewScheduleLocationForm(instance=schedule) # return render(request,'interviews/schedule_interview_location_form.html',{'form':form,'schedule':schedule}) - + def onsite_interview_list_view(request): diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py index ae8fb18..ada854a 100644 --- a/recruitment/views_frontend.py +++ b/recruitment/views_frontend.py @@ -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' \ No newline at end of file + slug_url_kwarg = 'slug' diff --git a/templates/base.html b/templates/base.html index 404faaa..7ac566d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -122,6 +122,11 @@ {% endif %} {% endcomment %} + + @@ -340,6 +353,7 @@ + + + + {% comment %} {% if request.user.is_authenticated and request.user.is_staff %} diff --git a/templates/includes/paginator.html b/templates/includes/paginator.html index b56c3ca..d11e555 100644 --- a/templates/includes/paginator.html +++ b/templates/includes/paginator.html @@ -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 #}
  • - First
  • - + {# Previous Page Link #}
  • - Previous @@ -36,26 +36,26 @@
  • {% if page_obj.has_next %} - + {# Next Page Link #}
  • - Next
  • - + {# Last Page Link #}
  • - Last
  • {% endif %} - + {% endwith %} - + {% endif %} \ No newline at end of file diff --git a/templates/interviews/preview_schedule.html b/templates/interviews/preview_schedule.html index eb37dff..1d520ae 100644 --- a/templates/interviews/preview_schedule.html +++ b/templates/interviews/preview_schedule.html @@ -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 %}
    - +

    Interview Schedule Preview: **{{ job.title }}** @@ -98,13 +98,13 @@

    {% trans "Schedule Parameters" %}

    - +

    Working Hours: {{ start_time|time:"g:i A" }} to {{ end_time|time:"g:i A" }}

    Interview Duration: {{ interview_duration }} minutes

    Buffer Time: {{ buffer_time }} minutes

    - +

    Interview Period: {{ start_date|date:"F j, Y" }} — {{ end_date|date:"F j, Y" }}

    Active Days: @@ -122,7 +122,7 @@

    Interview Type: {{interview_type}}

    - +
    {% trans "Daily Break Times" %}
    {% if breaks %}
    @@ -162,9 +162,9 @@ {% for item in schedule %} {{ item.date|date:"F j, Y" }} - {{ item.time|time:"g:i A" }} - {{ item.candidate.name }} - {{ item.candidate.email }} + {{ item.time|time:"g:i A" }} + {{ item.applications.name }} + {{ item.applications.email }} {% endfor %} @@ -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" }}', diff --git a/templates/interviews/schedule_interviews.html b/templates/interviews/schedule_interviews.html index a50e158..31239ba 100644 --- a/templates/interviews/schedule_interviews.html +++ b/templates/interviews/schedule_interviews.html @@ -130,9 +130,9 @@ - {{ form.candidates }} - {% if form.candidates.errors %} -
    {{ form.candidates.errors }}
    + {{ form.applications }} + {% if form.applications.errors %} +
    {{ form.applications.errors }}
    {% endif %}
    diff --git a/templates/jobs/job_list.html b/templates/jobs/job_list.html index 1a78fda..68d7c86 100644 --- a/templates/jobs/job_list.html +++ b/templates/jobs/job_list.html @@ -285,7 +285,7 @@ {% trans "Offer" %} - + {% for job in jobs %} diff --git a/templates/meetings/list_meetings.html b/templates/meetings/list_meetings.html index 391bdd9..8dfbdb2 100644 --- a/templates/meetings/list_meetings.html +++ b/templates/meetings/list_meetings.html @@ -318,7 +318,7 @@ {{ meeting.topic }} {% if meeting.interview %} - {{ meeting.interview.candidate.name }} + {{ meeting.interview.candidate.name }} {% else %} +

    + +
    + + + + +{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/messages/message_list.html b/templates/messages/message_list.html new file mode 100644 index 0000000..e506f70 --- /dev/null +++ b/templates/messages/message_list.html @@ -0,0 +1,230 @@ +{% extends "base.html" %} +{% load static %} + +{% block title %}Messages{% endblock %} + +{% block content %} +
    +
    +
    +
    +

    Messages

    + + Compose Message + +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    Total Messages
    +

    {{ total_messages }}

    +
    +
    +
    +
    +
    +
    +
    Unread Messages
    +

    {{ unread_messages }}

    +
    +
    +
    +
    + + +
    +
    + {% if page_obj %} +
    + + + + + + + + + + + + + + {% for message in page_obj %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    SubjectSenderRecipientTypeStatusCreatedActions
    + + {{ message.subject }} + + {% if message.parent_message %} + Reply + {% endif %} + {{ message.sender.get_full_name|default:message.sender.username }}{{ message.recipient.get_full_name|default:message.recipient.username }} + + {{ message.get_message_type_display }} + + + {% if message.is_read %} + Read + {% else %} + Unread + {% endif %} + {{ message.created_at|date:"M d, Y H:i" }} +
    + + + + {% if not message.is_read and message.recipient == request.user %} + + + + {% endif %} + + + + + + +
    +
    + +

    No messages found.

    +

    Try adjusting your filters or compose a new message.

    +
    +
    + + + {% if page_obj.has_other_pages %} + + {% endif %} + {% else %} +
    + +

    No messages found.

    +

    Try adjusting your filters or compose a new message.

    + + Compose Message + +
    + {% endif %} +
    +
    +
    +
    +
    +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/people/create_person.html b/templates/people/create_person.html new file mode 100644 index 0000000..fbc6867 --- /dev/null +++ b/templates/people/create_person.html @@ -0,0 +1,444 @@ +{% extends "base.html" %} +{% load static i18n crispy_forms_tags %} + +{% block title %}Create Person - {{ block.super }}{% endblock %} + +{% block customCSS %} + +{% endblock %} + +{% block content %} +
    +
    + + + + +
    +

    + {% trans "Create New Person" %} +

    + + {% trans "Back to List" %} + +
    + + +
    +
    + {% if form.non_field_errors %} + + {% endif %} + +
    + {% csrf_token %} + + +
    +
    +
    +
    + +
    {% trans "Upload Profile Photo" %}
    +

    {% trans "Click to browse or drag and drop" %}

    +
    + +
    +
    +
    + + +
    +
    +
    + {% trans "Personal Information" %} +
    +
    +
    + {{ form.first_name|as_crispy_field }} +
    +
    + {{ form.middle_name|as_crispy_field }} +
    +
    + {{ form.last_name|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Contact Information" %} +
    +
    +
    + {{ form.email|as_crispy_field }} +
    +
    + {{ form.phone|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Additional Information" %} +
    +
    +
    + {{ form.date_of_birth|as_crispy_field }} +
    +
    + {{ form.nationality|as_crispy_field }} +
    +
    + {{ form.gender|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Address" %} +
    +
    +
    + {{ form.address }} +
    +
    + + + {% comment %}
    +
    +
    + {% trans "Professional Profile" %} +
    +
    +
    +
    + + + + {% trans "Optional: Add LinkedIn profile URL" %} + +
    +
    +
    {% endcomment %} + + +
    +
    +
    + + {% trans "Cancel" %} + +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} + +{% block customJS %} + + + + + + + + +{% endblock %} diff --git a/templates/people/person_detail.html b/templates/people/person_detail.html new file mode 100644 index 0000000..79c115a --- /dev/null +++ b/templates/people/person_detail.html @@ -0,0 +1,607 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %} + +{% block customCSS %} + +{% endblock %} + +{% block content %} +
    + + + + +
    +
    +
    + {% if person.profile_image %} + {{ person.get_full_name }} + {% else %} +
    + +
    + {% endif %} +
    +
    +

    {{ person.get_full_name }}

    + {% if person.email %} +

    + {{ person.email }} +

    + {% endif %} +
    + {% if person.nationality %} + + {{ person.nationality }} + + {% endif %} + {% if person.gender %} + + {% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %} + + {% endif %} + {% if person.user %} + + {% trans "User Account" %} + + {% endif %} +
    + {% if user.is_staff %} +
    + + {% trans "Edit Person" %} + + +
    + {% endif %} +
    +
    +
    + +
    + +
    +
    +
    +
    +
    {% trans "Personal Information" %}
    + +
    + + {% trans "Full Name" %}: + {{ person.get_full_name }} +
    + + {% if person.first_name %} +
    + + {% trans "First Name" %}: + {{ person.first_name }} +
    + {% endif %} + + {% if person.middle_name %} +
    + + {% trans "Middle Name" %}: + {{ person.middle_name }} +
    + {% endif %} + + {% if person.last_name %} +
    + + {% trans "Last Name" %}: + {{ person.last_name }} +
    + {% endif %} + + {% if person.date_of_birth %} +
    + + {% trans "Date of Birth" %}: + {{ person.date_of_birth }} +
    + {% endif %} + + {% if person.gender %} +
    + + {% trans "Gender" %}: + + {% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %} + +
    + {% endif %} + + {% if person.nationality %} +
    + + {% trans "Nationality" %}: + {{ person.nationality }} +
    + {% endif %} +
    +
    +
    +
    + + +
    +
    +
    +
    +
    {% trans "Contact Information" %}
    + + {% if person.email %} +
    + + {% trans "Email" %}: + + + {{ person.email }} + + +
    + {% endif %} + + {% if person.phone %} +
    + + {% trans "Phone" %}: + + + {{ person.phone }} + + +
    + {% endif %} + + {% if person.address %} +
    + + {% trans "Address" %}: + {{ person.address|linebreaksbr }} +
    + {% endif %} + + {% if person.linkedin_profile %} +
    + + {% trans "LinkedIn" %}: + + + {% trans "View Profile" %} + + + +
    + {% endif %} +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    +
    + {% trans "Applications" %} + {{ person.applications.count }} +
    + + {% if person.applications %} + {% for application in person.applications.all %} + + {% endfor %} + {% else %} +
    + +

    {% trans "No applications found" %}

    +
    + {% endif %} +
    +
    +
    + + +
    +
    +
    +
    + {% trans "Documents" %} + {{ person.documents.count }} +
    + + {% if person.documents %} + {% for document in person.documents %} + + {% endfor %} + {% else %} +
    + +

    {% trans "No documents found" %}

    +
    + {% endif %} +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + {% trans "System Information" %} +
    +
    +
    +
    + + {% trans "Created" %}: + {{ person.created_at|date:"d M Y H:i" }} +
    +
    +
    +
    + + {% trans "Last Updated" %}: + {{ person.updated_at|date:"d M Y H:i" }} +
    +
    + {% if person.user %} +
    +
    + + {% trans "User Account" %}: + + + {{ person.user.username }} + + +
    +
    + {% endif %} +
    +
    +
    +
    +
    + + +
    +
    +
    + + {% trans "Back to People" %} + + {% if user.is_staff %} +
    + + {% trans "Edit Person" %} + + +
    + {% endif %} +
    +
    +
    +
    +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/people/person_list.html b/templates/people/person_list.html new file mode 100644 index 0000000..a212326 --- /dev/null +++ b/templates/people/person_list.html @@ -0,0 +1,411 @@ +{% extends "base.html" %} +{% load static i18n %} + +{% block title %}People - {{ block.super }}{% endblock %} + +{% block customCSS %} + +{% endblock %} + +{% block content %} +
    + +
    +

    + {% trans "People Directory" %} +

    + + {% trans "Add New Person" %} + +
    + + +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    + +
    +
    + {% if request.GET.q %}{% endif %} + +
    + + +
    + +
    + + +
    + +
    +
    + + {% if request.GET.q or request.GET.nationality or request.GET.gender %} + + {% trans "Clear" %} + + {% endif %} +
    +
    +
    +
    +
    +
    +
    + + {% if people_list %} +
    + + {% include "includes/_list_view_switcher.html" with list_id="person-list" %} + + +
    +
    + + + + + + + + + + + + + + + + {% for person in people_list %} + + + + + + + + + + + + {% endfor %} + +
    {% trans "Photo" %}{% trans "Name" %}{% trans "Email" %}{% trans "Phone" %}{% trans "Nationality" %}{% trans "Gender" %}{% trans "Agency" %}{% trans "Created" %}{% trans "Actions" %}
    + {% if person.profile_image %} + {{ person.get_full_name }} + {% else %} +
    + +
    + {% endif %} +
    + + {{ person.full_name }} + + {{ person.email|default:"N/A" }}{{ person.phone|default:"N/A" }} + {% if person.nationality %} + {{ person.nationality }} + {% else %} + N/A + {% endif %} + + {% if person.gender %} + + {% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %} + + {% else %} + N/A + {% endif %} + {{ person.agency.name|default:"N/A" }}{{ person.created_at|date:"d-m-Y" }} +
    + + + + {% if user.is_staff %} + + + + + {% endif %} +
    +
    +
    +
    + + +
    + {% for person in people_list %} +
    +
    +
    +
    +
    + {% if person.profile_image %} + {{ person.get_full_name }} + {% else %} +
    + +
    + {% endif %} +
    +
    +
    + + {{ person.get_full_name }} + +
    +

    {{ person.email|default:"N/A" }}

    +
    +
    + +
    + {% if person.phone %} +
    + {{ person.phone }} +
    + {% endif %} + {% if person.nationality %} +
    + + {{ person.nationality }} +
    + {% endif %} + {% if person.gender %} +
    + + + {% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %} + +
    + {% endif %} + {% if person.date_of_birth %} +
    + {{ person.date_of_birth }} +
    + {% endif %} +
    + +
    +
    + + {% trans "View" %} + + {% if user.is_staff %} + + {% trans "Edit" %} + + + {% endif %} +
    +
    +
    +
    +
    + {% endfor %} +
    +
    + + + {% include "includes/paginator.html" %} + {% else %} + +
    +
    + +

    {% trans "No people found" %}

    +

    {% trans "Create your first person record." %}

    + {% if user.is_staff %} + + {% trans "Add Person" %} + + {% endif %} +
    +
    + {% endif %} +
    +{% endblock %} diff --git a/templates/people/update_person.html b/templates/people/update_person.html new file mode 100644 index 0000000..788cb56 --- /dev/null +++ b/templates/people/update_person.html @@ -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 %} + +{% endblock %} + +{% block content %} +
    +
    + + + + +
    +

    + {% trans "Update Person" %} +

    + +
    + + +
    +
    +
    +
    {% trans "Currently Editing" %}
    +
    + {% if person.profile_image %} + {{ person.get_full_name }} + {% else %} +
    + +
    + {% endif %} +
    +
    {{ person.get_full_name }}
    + {% if person.email %} +

    {{ person.email }}

    + {% endif %} + + {% trans "Created" %}: {{ person.created_at|date:"d M Y" }} • + {% trans "Last Updated" %}: {{ person.updated_at|date:"d M Y" }} + +
    +
    +
    +
    +
    + + +
    +
    + {% if form.non_field_errors %} + + {% endif %} + + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
    + {% csrf_token %} + + +
    +
    +
    +
    + {% if person.profile_image %} + Current Profile +
    {% trans "Click to change photo" %}
    +

    {% trans "Current photo will be replaced" %}

    + {% else %} + +
    {% trans "Upload Profile Photo" %}
    +

    {% trans "Click to browse or drag and drop" %}

    + {% endif %} +
    + +
    + {% if person.profile_image %} +
    + + {% trans "Leave empty to keep current photo" %} + +
    + {% endif %} +
    +
    + + +
    +
    +
    + {% trans "Personal Information" %} +
    +
    +
    + {{ form.first_name|as_crispy_field }} +
    +
    + {{ form.middle_name|as_crispy_field }} +
    +
    + {{ form.last_name|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Contact Information" %} +
    +
    +
    + {{ form.email|as_crispy_field }} +
    +
    + {{ form.phone|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Additional Information" %} +
    +
    +
    + {{ form.date_of_birth|as_crispy_field }} +
    +
    + {{ form.nationality|as_crispy_field }} +
    +
    + {{ form.gender|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Address Information" %} +
    +
    +
    + {{ form.address|as_crispy_field }} +
    +
    + + +
    +
    +
    + {% trans "Professional Profile" %} +
    +
    +
    +
    + + + + {% trans "Optional: Add LinkedIn profile URL" %} + +
    +
    +
    + + +
    +
    +
    + +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/agency_base.html b/templates/portal_base.html similarity index 83% rename from templates/agency_base.html rename to templates/portal_base.html index 03b71e3..7386ebf 100644 --- a/templates/agency_base.html +++ b/templates/portal_base.html @@ -49,16 +49,23 @@ - + {# Using inline style for nav background color - replace with a dedicated CSS class (e.g., .bg-kaauh-nav) if defined in main.css #} -
    +
    @@ -146,7 +175,7 @@ - + {# JavaScript (Left unchanged as it was mostly correct) #} + + +{% endblock %} diff --git a/templates/recruitment/agency_portal_submit_candidate.html b/templates/recruitment/agency_portal_submit_candidate.html index 42b4793..7b1fc5c 100644 --- a/templates/recruitment/agency_portal_submit_candidate.html +++ b/templates/recruitment/agency_portal_submit_candidate.html @@ -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 %} diff --git a/templates/recruitment/candidate_create.html b/templates/recruitment/candidate_create.html index 185ea9f..c31161b 100644 --- a/templates/recruitment/candidate_create.html +++ b/templates/recruitment/candidate_create.html @@ -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 %} +{% endblock %} + +{% block content %} + +{% endblock %} + +{% block customJS %} + +{% endblock %} \ No newline at end of file diff --git a/test_agency_access_links.py b/test_agency_access_links.py deleted file mode 100644 index 354e883..0000000 --- a/test_agency_access_links.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -import os -import sys -import django - -# Add project root to Python path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -# Set Django settings module -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') - -# Initialize Django -django.setup() - -def test_agency_access_links(): - """Test agency access link functionality""" - print("Testing agency access links...") - - # Test 1: Check if URLs exist - try: - from recruitment.urls import urlpatterns - print("✅ URL patterns loaded successfully") - - # Check if our new URLs are in patterns - url_patterns = [str(pattern.pattern) for pattern in urlpatterns] - - # Look for our specific URL patterns - deactivate_found = any('agency-access-links' in pattern and 'deactivate' in pattern for pattern in url_patterns) - reactivate_found = any('agency-access-links' in pattern and 'reactivate' in pattern for pattern in url_patterns) - - if deactivate_found: - print("✅ Found URL pattern for agency_access_link_deactivate") - else: - print("❌ Missing URL pattern for agency_access_link_deactivate") - - if reactivate_found: - print("✅ Found URL pattern for agency_access_link_reactivate") - else: - print("❌ Missing URL pattern for agency_access_link_reactivate") - - # Test 2: Check if views exist - try: - from recruitment.views import agency_access_link_deactivate, agency_access_link_reactivate - print("✅ View functions imported successfully") - - # Test that functions are callable - if callable(agency_access_link_deactivate): - print("✅ agency_access_link_deactivate is callable") - else: - print("❌ agency_access_link_deactivate is not callable") - - if callable(agency_access_link_reactivate): - print("✅ agency_access_link_reactivate is callable") - else: - print("❌ agency_access_link_reactivate is not callable") - - except ImportError as e: - print(f"❌ Import error: {e}") - - print("Agency access link functionality test completed!") - return True - except Exception as e: - print(f"❌ Test failed: {e}") - return False - -if __name__ == "__main__": - test_agency_access_links() diff --git a/test_agency_assignments.py b/test_agency_assignments.py deleted file mode 100644 index 586e93d..0000000 --- a/test_agency_assignments.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python -""" -Test script to verify agency assignment functionality -""" -import os -import sys -import django - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.test import Client -from django.urls import reverse -from django.contrib.auth.models import User -from recruitment.models import HiringAgency, JobPosting, AgencyJobAssignment - -def test_agency_assignments(): - """Test agency assignment functionality""" - print("🧪 Testing Agency Assignment Functionality") - print("=" * 50) - - # Create test client - client = Client() - - # Test URLs - urls_to_test = [ - ('agency_list', '/recruitment/agencies/'), - ('agency_assignment_list', '/recruitment/agency-assignments/'), - ] - - print("\n📋 Testing URL Accessibility:") - for url_name, expected_path in urls_to_test: - try: - url = reverse(url_name) - print(f"✅ {url_name}: {url}") - except Exception as e: - print(f"❌ {url_name}: Error - {e}") - - print("\n🔍 Testing Views:") - - # Test agency list view (without authentication - should redirect) - try: - response = client.get(reverse('agency_list')) - if response.status_code == 302: # Redirect to login - print("✅ Agency list view redirects unauthenticated users (as expected)") - else: - print(f"⚠️ Agency list view status: {response.status_code}") - except Exception as e: - print(f"❌ Agency list view error: {e}") - - # Test agency assignment list view (without authentication - should redirect) - try: - response = client.get(reverse('agency_assignment_list')) - if response.status_code == 302: # Redirect to login - print("✅ Agency assignment list view redirects unauthenticated users (as expected)") - else: - print(f"⚠️ Agency assignment list view status: {response.status_code}") - except Exception as e: - print(f"❌ Agency assignment list view error: {e}") - - print("\n📊 Testing Database Models:") - - # Test if models exist and can be created - try: - # Check if we can query the models - agency_count = HiringAgency.objects.count() - job_count = JobPosting.objects.count() - assignment_count = AgencyJobAssignment.objects.count() - - print(f"✅ HiringAgency model: {agency_count} agencies in database") - print(f"✅ JobPosting model: {job_count} jobs in database") - print(f"✅ AgencyJobAssignment model: {assignment_count} assignments in database") - - except Exception as e: - print(f"❌ Database model error: {e}") - - print("\n🎯 Navigation Menu Test:") - print("✅ Agency Assignments link added to navigation menu") - print("✅ Navigation includes both 'Agencies' and 'Agency Assignments' links") - - print("\n📝 Summary:") - print("✅ Agency assignment functionality is fully implemented") - print("✅ All required views are present in views.py") - print("✅ URL patterns are configured in urls.py") - print("✅ Navigation menu has been updated") - print("✅ Templates are created and functional") - - print("\n🚀 Ready for use!") - print("Users can now:") - print(" - View agencies at /recruitment/agencies/") - print(" - Manage agency assignments at /recruitment/agency-assignments/") - print(" - Create, update, and delete assignments") - print(" - Generate access links for external agencies") - print(" - Send messages to agencies") - -if __name__ == '__main__': - test_agency_assignments() diff --git a/test_agency_crud.py b/test_agency_crud.py deleted file mode 100644 index e2f9da1..0000000 --- a/test_agency_crud.py +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env python -""" -Test script to verify Agency CRUD functionality -""" -import os -import sys -import django - -# Add the project directory to the Python path -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -# Set up Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.test import Client -from django.contrib.auth.models import User -from recruitment.models import HiringAgency - -def test_agency_crud(): - """Test Agency CRUD operations""" - print("🧪 Testing Agency CRUD Functionality") - print("=" * 50) - - # Create a test user - user, created = User.objects.get_or_create( - username='testuser', - defaults={'email': 'test@example.com', 'is_staff': True, 'is_superuser': True} - ) - if created: - user.set_password('testpass123') - user.save() - print("✅ Created test user") - else: - print("ℹ️ Using existing test user") - - # Create test client - client = Client() - - # Login the user - client.login(username='testuser', password='testpass123') - print("✅ Logged in test user") - - # Test 1: Agency List View - print("\n1. Testing Agency List View...") - response = client.get('/recruitment/agencies/') - if response.status_code == 200: - print("✅ Agency list view works") - else: - print(f"❌ Agency list view failed: {response.status_code}") - return False - - # Test 2: Agency Create View (GET) - print("\n2. Testing Agency Create View (GET)...") - response = client.get('/recruitment/agencies/create/') - if response.status_code == 200: - print("✅ Agency create view works") - else: - print(f"❌ Agency create view failed: {response.status_code}") - return False - - # Test 3: Agency Create (POST) - print("\n3. Testing Agency Create (POST)...") - agency_data = { - 'name': 'Test Agency', - 'contact_person': 'John Doe', - 'email': 'test@agency.com', - 'phone': '+1234567890', - 'country': 'SA', - 'city': 'Riyadh', - 'address': 'Test Address', - 'website': 'https://testagency.com', - 'description': 'Test agency description' - } - - response = client.post('/recruitment/agencies/create/', agency_data) - if response.status_code == 302: # Redirect after successful creation - print("✅ Agency creation works") - - # Get the created agency - agency = HiringAgency.objects.filter(name='Test Agency').first() - if agency: - print(f"✅ Agency created with ID: {agency.id}") - - # Test 4: Agency Detail View - print("\n4. Testing Agency Detail View...") - response = client.get(f'/recruitment/agencies/{agency.slug}/') - if response.status_code == 200: - print("✅ Agency detail view works") - else: - print(f"❌ Agency detail view failed: {response.status_code}") - return False - - # Test 5: Agency Update View (GET) - print("\n5. Testing Agency Update View (GET)...") - response = client.get(f'/recruitment/agencies/{agency.slug}/update/') - if response.status_code == 200: - print("✅ Agency update view works") - else: - print(f"❌ Agency update view failed: {response.status_code}") - return False - - # Test 6: Agency Update (POST) - print("\n6. Testing Agency Update (POST)...") - update_data = agency_data.copy() - update_data['name'] = 'Updated Test Agency' - - response = client.post(f'/recruitment/agencies/{agency.slug}/update/', update_data) - if response.status_code == 302: - print("✅ Agency update works") - - # Verify the update - agency.refresh_from_db() - if agency.name == 'Updated Test Agency': - print("✅ Agency data updated correctly") - else: - print("❌ Agency data not updated correctly") - return False - else: - print(f"❌ Agency update failed: {response.status_code}") - return False - - # Test 7: Agency Delete View (GET) - print("\n7. Testing Agency Delete View (GET)...") - response = client.get(f'/recruitment/agencies/{agency.slug}/delete/') - if response.status_code == 200: - print("✅ Agency delete view works") - else: - print(f"❌ Agency delete view failed: {response.status_code}") - return False - - # Test 8: Agency Delete (POST) - print("\n8. Testing Agency Delete (POST)...") - delete_data = { - 'confirm_name': 'Updated Test Agency', - 'confirm_delete': 'on' - } - - response = client.post(f'/recruitment/agencies/{agency.slug}/delete/', delete_data) - if response.status_code == 302: - print("✅ Agency deletion works") - - # Verify deletion - if not HiringAgency.objects.filter(name='Updated Test Agency').exists(): - print("✅ Agency deleted successfully") - else: - print("❌ Agency not deleted") - return False - else: - print(f"❌ Agency deletion failed: {response.status_code}") - return False - - else: - print("❌ Agency not found after creation") - return False - else: - print(f"❌ Agency creation failed: {response.status_code}") - print(f"Response content: {response.content.decode()}") - return False - - # Test 9: URL patterns - print("\n9. Testing URL patterns...") - try: - from django.urls import reverse - print(f"✅ Agency list URL: {reverse('agency_list')}") - print(f"✅ Agency create URL: {reverse('agency_create')}") - print("✅ All URL patterns resolved correctly") - except Exception as e: - print(f"❌ URL pattern error: {e}") - return False - - # Test 10: Model functionality - print("\n10. Testing Model functionality...") - try: - # Test model creation - test_agency = HiringAgency.objects.create( - name='Model Test Agency', - contact_person='Jane Smith', - email='model@test.com', - phone='+9876543210', - country='SA' - ) - print(f"✅ Model creation works: {test_agency.name}") - print(f"✅ Slug generation works: {test_agency.slug}") - print(f"✅ String representation works: {str(test_agency)}") - - # Test model methods - print(f"✅ Country display: {test_agency.get_country_display()}") - - # Clean up - test_agency.delete() - print("✅ Model deletion works") - - except Exception as e: - print(f"❌ Model functionality error: {e}") - return False - - print("\n" + "=" * 50) - print("🎉 All Agency CRUD tests passed!") - return True - -if __name__ == '__main__': - success = test_agency_crud() - sys.exit(0 if success else 1) diff --git a/test_agency_isolation.py b/test_agency_isolation.py deleted file mode 100644 index 3ffa2bc..0000000 --- a/test_agency_isolation.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python -""" -Test script to verify agency user isolation and all fixes are working properly. -This tests: -1. Agency login functionality (AttributeError fix) -2. Agency portal template isolation (agency_base.html usage) -3. Agency user access restrictions -4. JavaScript fixes in submit candidate form -""" - -import os -import sys -import django -from django.test import TestCase, Client -from django.urls import reverse -from django.contrib.auth.models import User -from unittest.mock import patch, MagicMock - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from recruitment.models import Agency, AgencyJobAssignment, AgencyAccessLink, Candidate, Job - - -class AgencyIsolationTest(TestCase): - """Test agency user isolation and functionality""" - - def setUp(self): - """Set up test data""" - # Create internal staff user - self.staff_user = User.objects.create_user( - username='staff_user', - email='staff@example.com', - password='testpass123', - is_staff=True - ) - - # Create agency user - self.agency_user = User.objects.create_user( - username='agency_user', - email='agency@example.com', - password='testpass123', - is_staff=False - ) - - # Create agency - self.agency = Agency.objects.create( - name='Test Agency', - contact_email='agency@example.com', - contact_phone='+1234567890', - address='Test Address', - is_active=True - ) - - # Create job - self.job = Job.objects.create( - title='Test Job', - department='IT', - description='Test job description', - status='active' - ) - - # Create agency assignment - self.assignment = AgencyJobAssignment.objects.create( - agency=self.agency, - job=self.job, - max_candidates=10, - deadline_date='2024-12-31', - status='active' - ) - - # Create access link - self.access_link = AgencyAccessLink.objects.create( - assignment=self.assignment, - unique_token='test-token-123', - access_password='testpass123', - expires_at='2024-12-31' - ) - - # Create test candidate - self.candidate = Candidate.objects.create( - first_name='Test', - last_name='Candidate', - email='candidate@example.com', - phone='+1234567890', - job=self.job, - source='agency', - hiring_agency=self.agency - ) - - def test_agency_login_form_attribute_error_fix(self): - """Test that AgencyLoginForm handles missing validated_access_link attribute""" - from recruitment.forms import AgencyLoginForm - - # Test form with valid data - form_data = { - 'access_token': 'test-token-123', - 'password': 'testpass123' - } - - form = AgencyLoginForm(data=form_data) - - # This should not raise AttributeError anymore - try: - is_valid = form.is_valid() - print(f"✓ AgencyLoginForm validation works: {is_valid}") - except AttributeError as e: - if 'validated_access_link' in str(e): - self.fail("AttributeError 'validated_access_link' not fixed!") - else: - raise - - def test_agency_portal_templates_use_agency_base(self): - """Test that agency portal templates use agency_base.html""" - agency_portal_templates = [ - 'recruitment/agency_portal_login.html', - 'recruitment/agency_portal_dashboard.html', - 'recruitment/agency_portal_submit_candidate.html', - 'recruitment/agency_portal_messages.html', - 'recruitment/agency_access_link_detail.html' - ] - - for template_name in agency_portal_templates: - template_path = f'templates/{template_name}' - if os.path.exists(template_path): - with open(template_path, 'r') as f: - content = f.read() - self.assertIn("{% extends 'agency_base.html' %}", content, - f"{template_name} should use agency_base.html") - print(f"✓ {template_name} uses agency_base.html") - else: - print(f"⚠ Template {template_name} not found") - - def test_agency_base_template_isolation(self): - """Test that agency_base.html properly isolates agency users""" - agency_base_path = 'templates/agency_base.html' - - if os.path.exists(agency_base_path): - with open(agency_base_path, 'r') as f: - content = f.read() - - # Check that it extends base.html - self.assertIn("{% extends 'base.html' %}", content) - - # Check that it has agency-specific navigation - self.assertIn('agency_portal_dashboard', content) - self.assertIn('agency_portal_logout', content) - - # Check that it doesn't include admin navigation - self.assertNotIn('admin:', content) - - print("✓ agency_base.html properly configured") - else: - self.fail("agency_base.html not found") - - def test_agency_login_view(self): - """Test agency login functionality""" - client = Client() - - # Test GET request - response = client.get(reverse('agency_portal_login')) - self.assertEqual(response.status_code, 200) - print("✓ Agency login page loads") - - # Test POST with valid credentials - response = client.post(reverse('agency_portal_login'), { - 'access_token': 'test-token-123', - 'password': 'testpass123' - }) - - # Should redirect or show success (depending on implementation) - self.assertIn(response.status_code, [200, 302]) - print("✓ Agency login POST request handled") - - def test_agency_user_access_restriction(self): - """Test that agency users can't access internal pages""" - client = Client() - - # Log in as agency user - client.login(username='agency_user', password='testpass123') - - # Try to access internal pages (should be restricted) - internal_urls = [ - '/admin/', - reverse('agency_list'), - reverse('candidate_list'), - ] - - for url in internal_urls: - try: - response = client.get(url) - # Agency users should get redirected or forbidden - self.assertIn(response.status_code, [302, 403, 404]) - print(f"✓ Agency user properly restricted from {url}") - except: - print(f"⚠ Could not test access to {url}") - - def test_javascript_fixes_in_submit_candidate(self): - """Test that JavaScript fixes are in place in submit candidate template""" - template_path = 'templates/recruitment/agency_portal_submit_candidate.html' - - if os.path.exists(template_path): - with open(template_path, 'r') as f: - content = f.read() - - # Check for safe element access patterns - self.assertIn('getElementValue', content) - self.assertIn('if (element)', content) - - # Check for error handling - self.assertIn('console.error', content) - - print("✓ JavaScript fixes present in submit candidate template") - else: - self.fail("agency_portal_submit_candidate.html not found") - - def test_agency_portal_navigation(self): - """Test agency portal navigation links""" - agency_portal_urls = [ - 'agency_portal_dashboard', - 'agency_portal_login', - 'agency_portal_logout', - ] - - for url_name in agency_portal_urls: - try: - url = reverse(url_name) - print(f"✓ Agency portal URL {url_name} resolves: {url}") - except: - print(f"⚠ Agency portal URL {url_name} not found") - - -def run_tests(): - """Run all tests""" - print("=" * 60) - print("AGENCY ISOLATION AND FIXES TEST") - print("=" * 60) - - test_case = AgencyIsolationTest() - test_case.setUp() - - tests = [ - test_case.test_agency_login_form_attribute_error_fix, - test_case.test_agency_portal_templates_use_agency_base, - test_case.test_agency_base_template_isolation, - test_case.test_agency_login_view, - test_case.test_agency_user_access_restriction, - test_case.test_javascript_fixes_in_submit_candidate, - test_case.test_agency_portal_navigation, - ] - - passed = 0 - failed = 0 - - for test in tests: - try: - test() - passed += 1 - except Exception as e: - print(f"✗ {test.__name__} failed: {e}") - failed += 1 - - print("=" * 60) - print(f"TEST RESULTS: {passed} passed, {failed} failed") - print("=" * 60) - - if failed == 0: - print("🎉 All tests passed! Agency isolation is working properly.") - else: - print("⚠️ Some tests failed. Please review the issues above.") - - return failed == 0 - - -if __name__ == '__main__': - success = run_tests() - sys.exit(0 if success else 1) diff --git a/test_async_email.py b/test_async_email.py deleted file mode 100644 index a90c1d0..0000000 --- a/test_async_email.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -""" -Test script for async email functionality -""" - -import os -import sys -import django - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.contrib.auth.models import User -from recruitment.email_service import send_bulk_email -from recruitment.models import JobPosting, Candidate - -def test_async_email(): - """Test async email sending functionality""" - - print("🧪 Testing Async Email Functionality") - print("=" * 50) - - try: - # Get a test user - test_user = User.objects.first() - if not test_user: - print("❌ No users found in database. Please create a user first.") - return - - # Get a test job and candidate - test_job = JobPosting.objects.first() - test_candidate = Candidate.objects.first() - - if not test_job or not test_candidate: - print("❌ No test job or candidate found. Please create some test data first.") - return - - print(f"📧 Test User: {test_user.email}") - print(f"💼 Test Job: {test_job.title}") - print(f"👤 Test Candidate: {test_candidate.name}") - - # Test synchronous email sending - print("\n1. Testing Synchronous Email Sending...") - try: - sync_result = send_bulk_email( - subject="Test Synchronous Email", - message="This is a test synchronous email from the ATS system.", - recipient_list=[test_user.email], - async_task=False - ) - print(f" ✅ Sync result: {sync_result}") - except Exception as e: - print(f" ❌ Sync error: {e}") - - # Test asynchronous email sending - print("\n2. Testing Asynchronous Email Sending...") - try: - async_result = send_bulk_email( - subject="Test Asynchronous Email", - message="This is a test asynchronous email from the ATS system.", - recipient_list=[test_user.email], - async_task=True - ) - print(f" ✅ Async result: {async_result}") - except Exception as e: - print(f" ❌ Async error: {e}") - - print("\n3. Testing Email Service Status...") - - # Check Django Q configuration - try: - import django_q - from django_q.models import Task - pending_tasks = Task.objects.count() - print(f" 📊 Django Q Status: Installed, {pending_tasks} tasks in queue") - except ImportError: - print(" ⚠️ Django Q not installed") - except Exception as e: - print(f" 📊 Django Q Status: Installed but error checking status: {e}") - - # Check email backend configuration - from django.conf import settings - email_backend = getattr(settings, 'EMAIL_BACKEND', 'not configured') - print(f" 📧 Email Backend: {email_backend}") - - email_host = getattr(settings, 'EMAIL_HOST', 'not configured') - print(f" 🌐 Email Host: {email_host}") - - email_port = getattr(settings, 'EMAIL_PORT', 'not configured') - print(f" 🔌 Email Port: {email_port}") - - print("\n✅ Async email functionality test completed!") - print("💡 If emails are not being received, check:") - print(" - Email server configuration in settings.py") - print(" - Django Q cluster status (python manage.py qmonitor)") - print(" - Email logs and spam folders") - - except Exception as e: - print(f"❌ Test failed with error: {e}") - import traceback - traceback.print_exc() - -if __name__ == "__main__": - test_async_email() diff --git a/test_csv_export.py b/test_csv_export.py deleted file mode 100644 index 25ac28d..0000000 --- a/test_csv_export.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env python -""" -Test script to verify CSV export functionality with updated JSON structure -""" -import os -import sys -import django - -# Setup Django environment -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from recruitment.models import Candidate, JobPosting -from recruitment.views_frontend import export_candidates_csv -from django.test import RequestFactory -from django.contrib.auth.models import User - -def test_csv_export(): - """Test the CSV export function with sample data""" - - print("🧪 Testing CSV Export Functionality") - print("=" * 50) - - # Create a test request factory - factory = RequestFactory() - - # Get or create a test user - user, created = User.objects.get_or_create( - username='testuser', - defaults={'email': 'test@example.com', 'is_staff': True} - ) - - # Get a sample job - job = JobPosting.objects.first() - if not job: - print("❌ No jobs found in database. Please create a job first.") - return False - - print(f"📋 Using job: {job.title}") - - # Test different stages - stages = ['screening', 'exam', 'interview', 'offer', 'hired'] - - for stage in stages: - print(f"\n🔍 Testing stage: {stage}") - - # Create a mock request - request = factory.get(f'/export/{job.slug}/{stage}/') - request.user = user - request.GET = {'search': ''} - - try: - # Call the export function - response = export_candidates_csv(request, job.slug, stage) - - # Check if response is successful - if response.status_code == 200: - print(f"✅ {stage} export successful") - - # Read and analyze the CSV content - content = response.content.decode('utf-8-sig') - lines = content.split('\n') - - if len(lines) > 1: - headers = lines[0].split(',') - print(f"📊 Headers: {len(headers)} columns") - print(f"📊 Data rows: {len(lines) - 1}") - - # Check for AI score column - if 'Match Score' in headers: - print("✅ Match Score column found") - else: - print("⚠️ Match Score column not found") - - # Check for other AI columns - ai_columns = ['Years Experience', 'Screening Rating', 'Professional Category', 'Top 3 Skills'] - found_ai_columns = [col for col in ai_columns if col in headers] - print(f"🤖 AI columns found: {found_ai_columns}") - - else: - print("⚠️ No data rows found") - - else: - print(f"❌ {stage} export failed with status: {response.status_code}") - - except Exception as e: - print(f"❌ {stage} export error: {str(e)}") - import traceback - traceback.print_exc() - - # Test with actual candidate data - print(f"\n🔍 Testing with actual candidate data") - candidates = Candidate.objects.filter(job=job) - print(f"📊 Total candidates for job: {candidates.count()}") - - if candidates.exists(): - # Test AI data extraction for first candidate - candidate = candidates.first() - print(f"\n🧪 Testing AI data extraction for: {candidate.name}") - - try: - # Test the model properties - print(f"📊 Match Score: {candidate.match_score}") - print(f"📊 Years Experience: {candidate.years_of_experience}") - print(f"📊 Screening Rating: {candidate.screening_stage_rating}") - print(f"📊 Professional Category: {candidate.professional_category}") - print(f"📊 Top 3 Skills: {candidate.top_3_keywords}") - print(f"📊 Strengths: {candidate.strengths}") - print(f"📊 Weaknesses: {candidate.weaknesses}") - - # Test AI analysis data structure - if candidate.ai_analysis_data: - print(f"📊 AI Analysis Data keys: {list(candidate.ai_analysis_data.keys())}") - if 'analysis_data' in candidate.ai_analysis_data: - analysis_keys = list(candidate.ai_analysis_data['analysis_data'].keys()) - print(f"📊 Analysis Data keys: {analysis_keys}") - else: - print("⚠️ 'analysis_data' key not found in ai_analysis_data") - else: - print("⚠️ No AI analysis data found") - - except Exception as e: - print(f"❌ Error extracting AI data: {str(e)}") - import traceback - traceback.print_exc() - - print("\n🎉 CSV Export Test Complete!") - return True - -if __name__ == '__main__': - test_csv_export() diff --git a/test_email_attachments.py b/test_email_attachments.py deleted file mode 100644 index 040d695..0000000 --- a/test_email_attachments.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -""" -Test script for email attachment functionality -""" - -import os -import django -from django.conf import settings -from django.test import TestCase, Client -from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.files.base import ContentFile -import io -from unittest.mock import Mock -from recruitment.email_service import send_bulk_email -from recruitment.forms import CandidateEmailForm -from recruitment.models import JobPosting, Candidate -from django.test import RequestFactory -from django.contrib.auth.models import User - -# Configure Django settings -if not settings.configured: - settings.configure( - DEBUG=True, - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } - }, - USE_TZ=True, - SECRET_KEY='test-secret-key', - INSTALLED_APPS=[ - 'django.contrib.contenttypes', - 'django.contrib.auth', - 'django.contrib.sessions', - 'recruitment', - ], - EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend', - ) - -# Setup test database -from django.db import connection -from django.core.management import execute_from_command_line - -def setup_test_data(): - """Create test data for email attachment testing""" - # Create test user - user = User.objects.create_user( - username='testuser', - email='test@example.com', - first_name='Test', - last_name='User' - ) - - # Create test job - job = JobPosting.objects.create( - title='Test Job Position', - description='This is a test job for email attachment testing.', - status='ACTIVE', - internal_job_id='TEST-001' - ) - - # Create test candidate - candidate = Candidate.objects.create( - first_name='John', - last_name='Doe', - email='john.doe@example.com', - phone='+1234567890', - address='123 Test Street', - job=job, - stage='Interview' - ) - - return user, job, candidate - -def test_email_service_with_attachments(): - """Test the email service directly with attachments""" - print("Testing email service with attachments...") - - # Create test files - test_files = [] - - # Test 1: Simple text file - text_content = "This is a test attachment content." - text_file = ContentFile( - text_content.encode('utf-8'), - name='test_document.txt' - ) - test_files.append(('test_document.txt', text_file, 'text/plain')) - - # Test 2: PDF content (simulated) - pdf_content = b'%PDF-1.4\n1 0 obj\n<<\n/Length 100\n>>stream\nxref\nstartxref\n1234\n5678\n/ModDate(D:20250101)\n' - pdf_file = ContentFile( - pdf_content, - name='test_document.pdf' - ) - test_files.append(('test_document.pdf', pdf_file, 'application/pdf')) - - # Test 3: Image file (simulated) - image_content = b'\x89PNG\r\n\x8a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 diff --git a/test_email_attachments_clean.py b/test_email_attachments_clean.py deleted file mode 100644 index 15c50ea..0000000 --- a/test_email_attachments_clean.py +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env python -""" -Clean test script for email attachment functionality -""" - -import os -import sys -import django -from django.conf import settings - -# Configure Django settings BEFORE importing any Django modules -if not settings.configured: - settings.configure( - DEBUG=True, - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } - }, - USE_TZ=True, - SECRET_KEY='test-secret-key', - INSTALLED_APPS=[ - 'django.contrib.contenttypes', - 'django.contrib.auth', - 'django.contrib.sessions', - 'recruitment', - ], - EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend', - MEDIA_ROOT='/tmp/test_media', - FILE_UPLOAD_TEMP_DIR='/tmp/test_uploads', - ) - -# Setup Django -django.setup() - -# Now import Django modules -from django.test import TestCase, Client -from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.files.base import ContentFile -import io -from unittest.mock import Mock -from recruitment.email_service import send_bulk_email -from recruitment.forms import CandidateEmailForm -from recruitment.models import JobPosting, Candidate -from django.test import RequestFactory -from django.contrib.auth.models import User - -# Setup test database -from django.db import connection -from django.core.management import execute_from_command_line - -def setup_test_data(): - """Create test data for email attachment testing""" - # Create test user - user = User.objects.create_user( - username='testuser', - email='test@example.com', - first_name='Test', - last_name='User' - ) - - # Create test job - job = JobPosting.objects.create( - title='Test Job Position', - description='This is a test job for email attachment testing.', - status='ACTIVE', - internal_job_id='TEST-001' - ) - - # Create test candidate - candidate = Candidate.objects.create( - first_name='John', - last_name='Doe', - email='john.doe@example.com', - phone='+1234567890', - address='123 Test Street', - job=job, - stage='Interview' - ) - - return user, job, candidate - -def test_email_service_with_attachments(): - """Test the email service directly with attachments""" - print("Testing email service with attachments...") - - # Create test files - test_files = [] - - # Test 1: Simple text file - text_content = "This is a test attachment content." - text_file = ContentFile( - text_content.encode('utf-8'), - name='test_document.txt' - ) - test_files.append(('test_document.txt', text_file, 'text/plain')) - - # Test 2: PDF content (simulated) - pdf_content = b'%PDF-1.4\n1 0 obj\n<<\n/Length 100\n>>stream\nxref\nstartxref\n1234\n5678\n/ModDate(D:20250101)\n' - pdf_file = ContentFile( - pdf_content, - name='test_document.pdf' - ) - test_files.append(('test_document.pdf', pdf_file, 'application/pdf')) - - # Test 3: Image file (simulated PNG header) - image_content = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01' - image_file = ContentFile( - image_content, - name='test_image.png' - ) - test_files.append(('test_image.png', image_file, 'image/png')) - - try: - # Test email service with attachments - result = send_bulk_email( - subject='Test Email with Attachments', - body='This is a test email with attachments.', - from_email='test@example.com', - recipient_list=['recipient@example.com'], - attachments=test_files - ) - - print(f"Email service result: {result}") - print("✓ Email service with attachments test passed") - return True - - except Exception as e: - print(f"✗ Email service test failed: {e}") - return False - -def test_candidate_email_form_with_attachments(): - """Test the CandidateEmailForm with attachments""" - print("\nTesting CandidateEmailForm with attachments...") - - user, job, candidate = setup_test_data() - - # Create test files for form - text_file = SimpleUploadedFile( - "test.txt", - b"This is test content for form attachment" - ) - - pdf_file = SimpleUploadedFile( - "test.pdf", - b"%PDF-1.4 test content" - ) - - form_data = { - 'subject': 'Test Subject', - 'body': 'Test body content', - 'from_email': 'test@example.com', - 'recipient_list': 'recipient@example.com', - } - - files_data = { - 'attachments': [text_file, pdf_file] - } - - try: - form = CandidateEmailForm(data=form_data, files=files_data) - - if form.is_valid(): - print("✓ Form validation passed") - print(f"Form cleaned data: {form.cleaned_data}") - - # Test form processing - try: - result = form.save() - print(f"✓ Form save result: {result}") - return True - except Exception as e: - print(f"✗ Form save failed: {e}") - return False - else: - print(f"✗ Form validation failed: {form.errors}") - return False - - except Exception as e: - print(f"✗ Form test failed: {e}") - return False - -def test_email_view_with_attachments(): - """Test the email view with attachments""" - print("\nTesting email view with attachments...") - - user, job, candidate = setup_test_data() - factory = RequestFactory() - - # Create a mock request with files - text_file = SimpleUploadedFile( - "test.txt", - b"This is test content for view attachment" - ) - - request = factory.post( - '/recruitment/send-candidate-email/', - data={ - 'subject': 'Test Subject', - 'body': 'Test body content', - 'from_email': 'test@example.com', - 'recipient_list': 'recipient@example.com', - }, - format='multipart' - ) - request.FILES['attachments'] = [text_file] - request.user = user - - try: - # Import and test the view - from recruitment.views import send_candidate_email - - response = send_candidate_email(request) - print(f"View response status: {response.status_code}") - - if response.status_code == 200: - print("✓ Email view test passed") - return True - else: - print(f"✗ Email view test failed with status: {response.status_code}") - return False - - except Exception as e: - print(f"✗ Email view test failed: {e}") - return False - -def main(): - """Run all email attachment tests""" - print("=" * 60) - print("EMAIL ATTACHMENT FUNCTIONALITY TESTS") - print("=" * 60) - - # Initialize Django - django.setup() - - # Create tables - from django.core.management import execute_from_command_line - execute_from_command_line(['manage.py', 'migrate', '--run-syncdb']) - - results = [] - - # Run tests - results.append(test_email_service_with_attachments()) - results.append(test_candidate_email_form_with_attachments()) - results.append(test_email_view_with_attachments()) - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - - passed = sum(results) - total = len(results) - - print(f"Tests passed: {passed}/{total}") - - if passed == total: - print("🎉 All email attachment tests passed!") - return True - else: - print("❌ Some email attachment tests failed!") - return False - -if __name__ == '__main__': - success = main() - exit(0 if success else 1) diff --git a/test_email_composition.py b/test_email_composition.py deleted file mode 100644 index 6e540b6..0000000 --- a/test_email_composition.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify email composition functionality -""" - -import os -import sys -import django - -# Setup Django environment -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.test import TestCase, Client -from django.urls import reverse -from django.contrib.auth.models import User -from django.utils import timezone -from recruitment.models import JobPosting, Candidate -from unittest.mock import patch, MagicMock - -def test_email_composition_view(): - """Test the email composition view""" - print("Testing email composition view...") - - # Create test user (delete if exists) - User.objects.filter(username='testuser').delete() - user = User.objects.create_user( - username='testuser', - email='test@example.com', - password='testpass123' - ) - - # Create test job - job = JobPosting.objects.create( - title='Test Job', - internal_job_id='TEST001', - description='Test job description', - status='active', - application_deadline=timezone.now() + timezone.timedelta(days=30) - ) - - # Add user to job participants so they appear in recipient choices - job.users.add(user) - - # Create test candidate - candidate = Candidate.objects.create( - first_name='Test Candidate', - last_name='', - email='candidate@example.com', - phone='1234567890', - job=job - ) - - # Create client and login - client = Client() - client.login(username='testuser', password='testpass123') - - # Test GET request to email composition view - url = reverse('compose_candidate_email', kwargs={ - 'job_slug': job.slug, - 'candidate_slug': candidate.slug - }) - - try: - response = client.get(url) - print(f"✓ GET request successful: {response.status_code}") - - if response.status_code == 200: - print("✓ Email composition form rendered successfully") - - # Check if form contains expected fields - content = response.content.decode('utf-8') - if 'subject' in content.lower(): - print("✓ Subject field found in form") - if 'message' in content.lower(): - print("✓ Message field found in form") - if 'recipients' in content.lower(): - print("✓ Recipients field found in form") - - else: - print(f"✗ Unexpected status code: {response.status_code}") - - except Exception as e: - print(f"✗ Error testing GET request: {e}") - - # Test POST request with mock email sending - post_data = { - 'subject': 'Test Subject', - 'message': 'Test message content', - 'recipients': ['candidate@example.com'], - 'include_candidate_info': True, - 'include_meeting_details': False - } - - with patch('django.core.mail.send_mass_mail') as mock_send_mail: - mock_send_mail.return_value = 1 - - try: - response = client.post(url, data=post_data) - print(f"✓ POST request successful: {response.status_code}") - - if response.status_code == 200: - # Check if JSON response is correct - try: - json_data = response.json() - if json_data.get('success'): - print("✓ Email sent successfully") - print(f"✓ Success message: {json_data.get('message')}") - else: - print(f"✗ Email send failed: {json_data.get('error')}") - except: - print("✗ Invalid JSON response") - else: - print(f"✗ Unexpected status code: {response.status_code}") - - except Exception as e: - print(f"✗ Error testing POST request: {e}") - - # Clean up - user.delete() - job.delete() - candidate.delete() - - print("Email composition test completed!") - -def test_email_form(): - """Test the CandidateEmailForm""" - print("\nTesting CandidateEmailForm...") - - from recruitment.forms import CandidateEmailForm - - # Create test user for form (delete if exists) - User.objects.filter(username='formuser').delete() - form_user = User.objects.create_user( - username='formuser', - email='form@example.com', - password='formpass123' - ) - - # Create test job and candidate for form - job = JobPosting.objects.create( - title='Test Job Form', - internal_job_id='TEST002', - description='Test job description for form', - status='active', - application_deadline=timezone.now() + timezone.timedelta(days=30) - ) - - # Add user to job participants so they appear in recipient choices - job.users.add(form_user) - - candidate = Candidate.objects.create( - first_name='Test Candidate', - last_name='Form', - email='candidate_form@example.com', - phone='1234567890', - job=job - ) - - try: - # Test valid form data - get available choices from form - form = CandidateEmailForm(job, candidate) - available_choices = [choice[0] for choice in form.fields['recipients'].choices] - - # Use first available choice for testing - test_recipient = available_choices[0] if available_choices else None - - if test_recipient: - form = CandidateEmailForm(job, candidate, data={ - 'subject': 'Test Subject', - 'message': 'Test message content', - 'recipients': [test_recipient], - 'include_candidate_info': True, - 'include_meeting_details': False - }) - - if form.is_valid(): - print("✓ Form validation passed") - print(f"✓ Cleaned recipients: {form.cleaned_data['recipients']}") - else: - print(f"✗ Form validation failed: {form.errors}") - else: - print("✗ No recipient choices available for testing") - except Exception as e: - print(f"✗ Error testing form: {e}") - - try: - # Test invalid form data (empty subject) - form = CandidateEmailForm(job, candidate, data={ - 'subject': '', - 'message': 'Test message content', - 'recipients': [], - 'include_candidate_info': True, - 'include_meeting_details': False - }) - - if not form.is_valid(): - print("✓ Form correctly rejected empty subject") - if 'subject' in form.errors: - print("✓ Subject field has validation error") - else: - print("✗ Form should have failed validation") - except Exception as e: - print(f"✗ Error testing invalid form: {e}") - - # Clean up - job.delete() - candidate.delete() - -if __name__ == '__main__': - print("Running Email Composition Tests") - print("=" * 50) - - test_email_form() - test_email_composition_view() - - print("\n" + "=" * 50) - print("All tests completed!") diff --git a/test_email_form_js.html b/test_email_form_js.html deleted file mode 100644 index edc579f..0000000 --- a/test_email_form_js.html +++ /dev/null @@ -1,507 +0,0 @@ - - - - - - Email Compose Form Test - - - - -
    -

    Email Compose Form JavaScript Test

    - - -
    -
    -
    -
    -
    -
    - - Compose Email -
    -
    -
    -
    - - - -
    - - -
    - - -
    - -
    -
    - - -
    -
    - - -
    -
    -
    - - -
    - - -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - Email will be sent to all selected recipients -
    -
    - - -
    -
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    - Loading... -
    -
    - Sending email... -
    -
    -
    -
    - - -
    -
    - - -
    -

    Test Results

    -
    -
    -
    - - - - - - - diff --git a/test_html_email_template.py b/test_html_email_template.py deleted file mode 100644 index 657be5d..0000000 --- a/test_html_email_template.py +++ /dev/null @@ -1,176 +0,0 @@ -#!/usr/bin/env python -""" -Test script for HTML email template functionality -""" - -import os -import sys -import django - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.template.loader import render_to_string -from django.utils.html import strip_tags -from recruitment.models import Candidate, JobPosting -from recruitment.email_service import send_interview_invitation_email - -def test_html_template(): - """Test the HTML email template rendering""" - print("Testing HTML email template...") - - # Create test context - context = { - 'candidate_name': 'John Doe', - 'candidate_email': 'john.doe@example.com', - 'candidate_phone': '+966 50 123 4567', - 'job_title': 'Senior Software Developer', - 'department': 'Information Technology', - 'company_name': 'Norah University', - 'meeting_topic': 'Interview for Senior Software Developer', - 'meeting_date_time': 'November 15, 2025 at 2:00 PM', - 'meeting_duration': '60 minutes', - 'join_url': 'https://zoom.us/j/123456789', - } - - try: - # Test template rendering - html_content = render_to_string('emails/interview_invitation.html', context) - plain_content = strip_tags(html_content) - - print("✅ HTML template rendered successfully!") - print(f"HTML content length: {len(html_content)} characters") - print(f"Plain text length: {len(plain_content)} characters") - - # Save rendered HTML to file for inspection - with open('test_interview_email.html', 'w', encoding='utf-8') as f: - f.write(html_content) - print("✅ HTML content saved to 'test_interview_email.html'") - - # Save plain text to file for inspection - with open('test_interview_email.txt', 'w', encoding='utf-8') as f: - f.write(plain_content) - print("✅ Plain text content saved to 'test_interview_email.txt'") - - return True - - except Exception as e: - print(f"❌ Error rendering template: {e}") - return False - -def test_email_service_function(): - """Test the email service function with mock data""" - print("\nTesting email service function...") - - try: - # Get a real candidate and job for testing - candidate = Candidate.objects.first() - job = JobPosting.objects.first() - - if not candidate: - print("❌ No candidates found in database") - return False - - if not job: - print("❌ No jobs found in database") - return False - - print(f"Using candidate: {candidate.name}") - print(f"Using job: {job.title}") - - # Test meeting details - meeting_details = { - 'topic': f'Interview for {job.title}', - 'date_time': 'November 15, 2025 at 2:00 PM', - 'duration': '60 minutes', - 'join_url': 'https://zoom.us/j/test123456', - } - - # Test the email function (without actually sending) - result = send_interview_invitation_email( - candidate=candidate, - job=job, - meeting_details=meeting_details, - recipient_list=['test@example.com'] - ) - - if result['success']: - print("✅ Email service function executed successfully!") - print(f"Recipients: {result.get('recipients_count', 'N/A')}") - print(f"Message: {result.get('message', 'N/A')}") - else: - print(f"❌ Email service function failed: {result.get('error', 'Unknown error')}") - - return result['success'] - - except Exception as e: - print(f"❌ Error testing email service: {e}") - return False - -def test_template_variables(): - """Test all template variables""" - print("\nTesting template variables...") - - # Test with minimal data - minimal_context = { - 'candidate_name': 'Test Candidate', - 'candidate_email': 'test@example.com', - 'job_title': 'Test Position', - } - - try: - html_content = render_to_string('emails/interview_invitation.html', minimal_context) - print("✅ Template works with minimal data") - - # Check for required variables - required_vars = ['candidate_name', 'candidate_email', 'job_title'] - missing_vars = [] - - for var in required_vars: - if f'{{ {var} }}' in html_content: - missing_vars.append(var) - - if missing_vars: - print(f"⚠️ Missing variables: {missing_vars}") - else: - print("✅ All required variables are present") - - return True - - except Exception as e: - print(f"❌ Error with minimal data: {e}") - return False - -def main(): - """Run all tests""" - print("🧪 Testing HTML Email Template System") - print("=" * 50) - - # Test 1: Template rendering - test1_passed = test_html_template() - - # Test 2: Template variables - test2_passed = test_template_variables() - - # Test 3: Email service function - test3_passed = test_email_service_function() - - # Summary - print("\n" + "=" * 50) - print("📊 TEST SUMMARY") - print(f"Template Rendering: {'✅ PASS' if test1_passed else '❌ FAIL'}") - print(f"Template Variables: {'✅ PASS' if test2_passed else '❌ FAIL'}") - print(f"Email Service: {'✅ PASS' if test3_passed else '❌ FAIL'}") - - overall_success = test1_passed and test2_passed and test3_passed - print(f"\nOverall Result: {'✅ ALL TESTS PASSED' if overall_success else '❌ SOME TESTS FAILED'}") - - if overall_success: - print("\n🎉 HTML email template system is ready!") - print("You can now send professional interview invitations using the new template.") - else: - print("\n🔧 Please fix the issues before using the template system.") - -if __name__ == '__main__': - main() diff --git a/test_interview_email.html b/test_interview_email.html deleted file mode 100644 index 6f84efb..0000000 --- a/test_interview_email.html +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Interview Invitation - - - -
    -
    -

    Interview Confirmation

    -
    - -

    Dear John Doe,

    -

    Thank you for your interest in the position. We are pleased to invite you to a virtual interview. Please find the details below.

    - -

    Interview Details

    -
    - Topic: Interview for Senior Software Developer -
    -
    - Date & Time: November 15, 2025 at 2:00 PM -
    -
    - Duration: 60 minutes -
    - - - - Join Interview Now - -

    Please click the button above to join the meeting at the scheduled time.

    - - -

    Your Information

    -
    - Name: John Doe -
    -
    - Email: john.doe@example.com -
    - -
    - Phone: +966 50 123 4567 -
    - - - -

    Position Details

    -
    - Position: Senior Software Developer -
    - -
    - Department: Information Technology -
    - - - - -
    - - diff --git a/test_interview_email.txt b/test_interview_email.txt deleted file mode 100644 index 5c42c4c..0000000 --- a/test_interview_email.txt +++ /dev/null @@ -1,139 +0,0 @@ - - - - - Interview Invitation - - /* Basic reset and typography */ - body { - font-family: Arial, sans-serif; - line-height: 1.6; - color: #333333; - margin: 0; - padding: 0; - background-color: #f4f4f4; - } - /* Container for the main content */ - .container { - max-width: 600px; - margin: 20px auto; - background-color: #ffffff; - padding: 30px; - border-radius: 8px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - } - /* Header styling */ - .header { - text-align: center; - border-bottom: 2px solid #007bff; - padding-bottom: 10px; - margin-bottom: 20px; - } - .header h1 { - color: #007bff; - font-size: 24px; - margin: 0; - } - /* Section headings */ - .section-header { - color: #007bff; - font-size: 18px; - margin-top: 25px; - margin-bottom: 10px; - border-left: 4px solid #007bff; - padding-left: 10px; - } - /* Key detail rows */ - .detail-row { - margin-bottom: 10px; - } - .detail-row strong { - display: inline-block; - width: 120px; - color: #555555; - } - /* Button style for the Join URL */ - .button { - display: block; - width: 80%; - margin: 25px auto; - padding: 12px 0; - background-color: #28a745; /* Success/Go color */ - color: #ffffff; - text-align: center; - text-decoration: none; - border-radius: 5px; - font-weight: bold; - font-size: 16px; - } - /* Footer/closing section */ - .footer { - margin-top: 30px; - padding-top: 15px; - border-top: 1px dashed #cccccc; - text-align: center; - font-size: 14px; - color: #777777; - } - - - - - - Interview Confirmation - - - Dear John Doe, - Thank you for your interest in the position. We are pleased to invite you to a virtual interview. Please find the details below. - - Interview Details - - Topic: Interview for Senior Software Developer - - - Date & Time: November 15, 2025 at 2:00 PM - - - Duration: 60 minutes - - - - - Join Interview Now - - Please click the button above to join the meeting at the scheduled time. - - - Your Information - - Name: John Doe - - - Email: john.doe@example.com - - - - Phone: +966 50 123 4567 - - - - - Position Details - - Position: Senior Software Developer - - - - Department: Information Technology - - - - - - We look forward to speaking with you. - If you have any questions, please reply to this email. - Best regards,The Norah University Team - - - - diff --git a/test_simple_email.py b/test_simple_email.py deleted file mode 100644 index b748cc5..0000000 --- a/test_simple_email.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python -""" -Simple test script for basic email functionality without attachments -""" - -import os -import sys -import django -from django.conf import settings - -# Configure Django settings BEFORE importing any Django modules -if not settings.configured: - settings.configure( - DEBUG=True, - DATABASES={ - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } - }, - USE_TZ=True, - SECRET_KEY='test-secret-key', - INSTALLED_APPS=[ - 'django.contrib.contenttypes', - 'django.contrib.auth', - 'django.contrib.sessions', - 'recruitment', - ], - EMAIL_BACKEND='django.core.mail.backends.console.EmailBackend', - ) - -# Setup Django -django.setup() - -# Now import Django modules -from django.test import TestCase, Client -from django.test import RequestFactory -from django.contrib.auth.models import User -from recruitment.email_service import send_bulk_email -from recruitment.forms import CandidateEmailForm -from recruitment.models import JobPosting, Candidate, Participants - -def setup_test_data(): - """Create test data for email testing""" - # Create test user (get or create to avoid duplicates) - user, created = User.objects.get_or_create( - username='testuser', - defaults={ - 'email': 'test@example.com', - 'first_name': 'Test', - 'last_name': 'User' - } - ) - - # Create test job - from datetime import datetime, timedelta - job = JobPosting.objects.create( - title='Test Job Position', - description='This is a test job for email testing.', - status='ACTIVE', - internal_job_id='TEST-001', - application_deadline=datetime.now() + timedelta(days=30) - ) - - # Create test candidate - candidate = Candidate.objects.create( - first_name='John', - last_name='Doe', - email='john.doe@example.com', - phone='+1234567890', - address='123 Test Street', - job=job, - stage='Interview' - ) - - # Create test participants - participant1 = Participants.objects.create( - name='Alice Smith', - email='alice@example.com', - phone='+1234567891', - designation='Interviewer' - ) - - participant2 = Participants.objects.create( - name='Bob Johnson', - email='bob@example.com', - phone='+1234567892', - designation='Hiring Manager' - ) - - # Add participants to job - job.participants.add(participant1, participant2) - - return user, job, candidate, [participant1, participant2] - -def test_email_service_basic(): - """Test the email service with basic functionality""" - print("Testing basic email service...") - - try: - # Test email service without attachments - result = send_bulk_email( - subject='Test Basic Email', - message='This is a test email without attachments.', - recipient_list=['recipient1@example.com', 'recipient2@example.com'] - ) - - print(f"Email service result: {result}") - print("✓ Basic email service test passed") - return True - - except Exception as e: - print(f"✗ Basic email service test failed: {e}") - return False - -def test_candidate_email_form_basic(): - """Test the CandidateEmailForm without attachments""" - print("\nTesting CandidateEmailForm without attachments...") - - user, job, candidate, participants = setup_test_data() - - form_data = { - 'subject': 'Test Subject', - 'message': 'Test body content', - 'recipients': [f'participant_{p.id}' for p in participants], - 'include_candidate_info': True, - 'include_meeting_details': True, - } - - try: - form = CandidateEmailForm(data=form_data, job=job, candidate=candidate) - - if form.is_valid(): - print("✓ Form validation passed") - print(f"Form cleaned data keys: {list(form.cleaned_data.keys())}") - - # Test getting email addresses - email_addresses = form.get_email_addresses() - print(f"Email addresses: {email_addresses}") - - # Test getting formatted message - formatted_message = form.get_formatted_message() - print(f"Formatted message length: {len(formatted_message)} characters") - - return True - else: - print(f"✗ Form validation failed: {form.errors}") - return False - - except Exception as e: - print(f"✗ Form test failed: {e}") - return False - -def test_email_sending_workflow(): - """Test the complete email sending workflow""" - print("\nTesting complete email sending workflow...") - - user, job, candidate, participants = setup_test_data() - - form_data = { - 'subject': 'Interview Update: John Doe - Test Job Position', - 'message': 'Please find the interview update below.', - 'recipients': [f'participant_{p.id}' for p in participants], - 'include_candidate_info': True, - 'include_meeting_details': True, - } - - try: - # Create and validate form - form = CandidateEmailForm(data=form_data, job=job, candidate=candidate) - - if not form.is_valid(): - print(f"✗ Form validation failed: {form.errors}") - return False - - # Get email data - subject = form.cleaned_data['subject'] - message = form.get_formatted_message() - recipient_emails = form.get_email_addresses() - - print(f"Subject: {subject}") - print(f"Recipients: {recipient_emails}") - print(f"Message preview: {message[:200]}...") - - # Send email using service - result = send_bulk_email( - subject=subject, - message=message, - recipient_list=recipient_emails - ) - - print(f"Email sending result: {result}") - print("✓ Complete email workflow test passed") - return True - - except Exception as e: - print(f"✗ Email workflow test failed: {e}") - return False - -def main(): - """Run all simple email tests""" - print("=" * 60) - print("SIMPLE EMAIL FUNCTIONALITY TESTS") - print("=" * 60) - - # Initialize Django - django.setup() - - # Create tables - from django.core.management import execute_from_command_line - execute_from_command_line(['manage.py', 'migrate', '--run-syncdb']) - - results = [] - - # Run tests - results.append(test_email_service_basic()) - results.append(test_candidate_email_form_basic()) - results.append(test_email_sending_workflow()) - - # Summary - print("\n" + "=" * 60) - print("TEST SUMMARY") - print("=" * 60) - - passed = sum(results) - total = len(results) - - print(f"Tests passed: {passed}/{total}") - - if passed == total: - print("🎉 All simple email tests passed!") - return True - else: - print("❌ Some simple email tests failed!") - return False - -if __name__ == '__main__': - success = main() - exit(0 if success else 1) diff --git a/test_sse.html b/test_sse.html deleted file mode 100644 index 7d24324..0000000 --- a/test_sse.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - SSE Test - - - -

    SSE Notification Test

    - -
    - Disconnected -
    - -
    - - - -
    - -

    Notifications:

    -
    -

    No notifications yet...

    -
    - -

    Test Instructions:

    -
      -
    1. Click "Connect" to start the SSE connection
    2. -
    3. Run the test script: python test_sse_notifications.py
    4. -
    5. Watch for real-time notifications to appear below
    6. -
    7. Check the browser console for debug information
    8. -
    - - - - diff --git a/test_sse_notifications.py b/test_sse_notifications.py deleted file mode 100644 index e79cd74..0000000 --- a/test_sse_notifications.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -""" -Test script to generate notifications and test SSE functionality -""" -import os -import sys -import django - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.utils import timezone -from django.contrib.auth.models import User -from recruitment.models import Notification - -def create_test_notification(): - """Create a test notification for admin user""" - try: - # Get first admin user - admin_user = User.objects.filter(is_staff=True).first() - if not admin_user: - print("No admin user found!") - return - - # Create a test notification - notification = Notification.objects.create( - recipient=admin_user, - notification_type=Notification.NotificationType.IN_APP, - message="Test SSE Notification - Real-time update working!", - status=Notification.Status.PENDING, - scheduled_for=timezone.now() # Add required scheduled_for field - ) - - print(f"Created test notification: {notification.id}") - print(f"Recipient: {admin_user.username}") - print(f"Message: {notification.message}") - print(f"Status: {notification.status}") - - return notification - - except Exception as e: - print(f"Error creating notification: {e}") - return None - -if __name__ == "__main__": - print("Testing SSE Notification System...") - print("=" * 50) - - notification = create_test_notification() - - if notification: - print("\n✅ Test notification created successfully!") - print("🔥 Check the browser console for SSE events") - print("📱 Open http://localhost:8000/ and look for real-time updates") - else: - print("\n❌ Failed to create test notification") diff --git a/test_sync_functionality.py b/test_sync_functionality.py deleted file mode 100644 index 797849b..0000000 --- a/test_sync_functionality.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for candidate sync functionality -""" - -import os -import sys -import django - -# Setup Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from recruitment.models import JobPosting, Candidate, Source -from recruitment.candidate_sync_service import CandidateSyncService -from django.utils import timezone - -def test_sync_service(): - """Test the candidate sync service""" - print("🧪 Testing Candidate Sync Service") - print("=" * 50) - - # Initialize sync service - sync_service = CandidateSyncService() - - # Get test data - print("📊 Getting test data...") - jobs = JobPosting.objects.all() - sources = Source.objects.filter(supports_outbound_sync=True) - - print(f"Found {jobs.count()} jobs") - print(f"Found {sources.count()} sources with outbound sync support") - - if not jobs.exists(): - print("❌ No jobs found. Creating test job...") - # Create a test job if none exists - job = JobPosting.objects.create( - title="Test Developer Position", - department="IT", - description="Test job for sync functionality", - application_deadline=timezone.now().date() + timezone.timedelta(days=30), - status="ACTIVE" - ) - print(f"✅ Created test job: {job.title}") - else: - job = jobs.first() - print(f"✅ Using existing job: {job.title}") - - if not sources.exists(): - print("❌ No sources with outbound sync found. Creating test source...") - # Create a test source if none exists - source = Source.objects.create( - name="Test ERP System", - source_type="ERP", - sync_endpoint="https://httpbin.org/post", # Test endpoint that echoes back requests - sync_method="POST", - test_method="POST", - supports_outbound_sync=True, - is_active=True, - custom_headers='{"Content-Type": "application/json", "Authorization": "Bearer test-token"}' - ) - print(f"✅ Created test source: {source.name}") - else: - source = sources.first() - print(f"✅ Using existing source: {source.name}") - - # Test connection - print("\n🔗 Testing source connection...") - try: - connection_result = sync_service.test_source_connection(source) - print(f"✅ Connection test result: {connection_result}") - except Exception as e: - print(f"❌ Connection test failed: {str(e)}") - - # Check for hired candidates - hired_candidates = job.candidates.filter(offer_status='Accepted') - print(f"\n👥 Found {hired_candidates.count()} hired candidates") - - if hired_candidates.exists(): - # Test sync for hired candidates - print("\n🔄 Testing sync for hired candidates...") - try: - results = sync_service.sync_hired_candidates_to_all_sources(job) - print("✅ Sync completed successfully!") - print(f"Results: {results}") - except Exception as e: - print(f"❌ Sync failed: {str(e)}") - else: - print("ℹ️ No hired candidates to sync. Creating test candidate...") - - # Create a test candidate if none exists - candidate = Candidate.objects.create( - job=job, - first_name="Test", - last_name="Candidate", - email="test@example.com", - phone="+1234567890", - address="Test Address", - stage="Offer", - offer_status="Accepted", - offer_date=timezone.now().date(), - ai_analysis_data={ - 'analysis_data': { - 'match_score': 85, - 'years_of_experience': 5, - 'screening_stage_rating': 'A - Highly Qualified' - } - } - ) - print(f"✅ Created test candidate: {candidate.name}") - - # Test sync with the new candidate - print("\n🔄 Testing sync with new candidate...") - try: - results = sync_service.sync_hired_candidates_to_all_sources(job) - print("✅ Sync completed successfully!") - print(f"Results: {results}") - except Exception as e: - print(f"❌ Sync failed: {str(e)}") - - print("\n🎯 Test Summary") - print("=" * 50) - print("✅ Candidate sync service is working correctly") - print("✅ Source connection testing works") - print("✅ Hired candidate sync functionality verified") - print("\n📝 Next Steps:") - print("1. Configure real source endpoints in the admin panel") - print("2. Test with actual external systems") - print("3. Monitor sync logs for production usage") - -if __name__ == "__main__": - test_sync_service() diff --git a/test_urls.py b/test_urls.py deleted file mode 100644 index 3053444..0000000 --- a/test_urls.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python -"""Test script to verify URL configuration""" - -import os -import sys -import django - -# Add the project directory to the Python path -sys.path.append('/home/ismail/projects/ats/kaauh_ats') - -# Set up Django -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'NorahUniversity.settings') -django.setup() - -from django.urls import reverse -from django.test import Client - -def test_urls(): - """Test the agency access link URLs""" - print("Testing agency access link URLs...") - - try: - # Test URL reverse lookup - deactivate_url = reverse('agency_access_link_deactivate', kwargs={'slug': 'test-slug'}) - print(f"✓ Deactivate URL: {deactivate_url}") - - reactivate_url = reverse('agency_access_link_reactivate', kwargs={'slug': 'test-slug'}) - print(f"✓ Reactivate URL: {reactivate_url}") - - # Test URL resolution - from django.urls import resolve - deactivate_view = resolve('/recruitment/agency-access-link/test-slug/deactivate/') - print(f"✓ Deactivate view: {deactivate_view.view_name}") - - reactivate_view = resolve('/recruitment/agency-access-link/test-slug/reactivate/') - print(f"✓ Reactivate view: {reactivate_view.view_name}") - - print("\n✅ All URL tests passed!") - return True - - except Exception as e: - print(f"❌ Error testing URLs: {e}") - return False - -if __name__ == '__main__': - test_urls() diff --git a/test_word_integration.py b/test_word_integration.py deleted file mode 100644 index de56739..0000000 --- a/test_word_integration.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to verify Word document integration in recruitment/tasks.py -""" - -import os -import sys -import tempfile - -# Add the project directory to Python path -sys.path.insert(0, '/home/ismail/projects/ats/kaauh_ats') - -# Import the tasks module -try: - from recruitment.tasks import extract_text_from_document, extract_text_from_pdf, extract_text_from_word - print("✓ Successfully imported text extraction functions") -except ImportError as e: - print(f"✗ Failed to import functions: {e}") - sys.exit(1) - -def test_pdf_extraction(): - """Test PDF text extraction with a sample PDF""" - print("\n--- Testing PDF Extraction ---") - - # Create a temporary PDF file for testing - with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_pdf: - try: - # Create a simple PDF content (this would normally be a real PDF) - tmp_pdf.write(b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n/Pages 2 0 R\n>>\nendobj\n") - tmp_pdf_path = tmp_pdf.name - - # Test the PDF extraction - text = extract_text_from_pdf(tmp_pdf_path) - print(f"✓ PDF extraction completed. Text length: {len(text)}") - - # Clean up - os.unlink(tmp_pdf_path) - - except Exception as e: - print(f"✗ PDF extraction failed: {e}") - -def test_word_extraction(): - """Test Word text extraction with a sample Word document""" - print("\n--- Testing Word Extraction ---") - - try: - # Check if python-docx is available - from recruitment.tasks import DOCX_AVAILABLE - if not DOCX_AVAILABLE: - print("⚠ python-docx not available. Skipping Word extraction test.") - return - - # Create a temporary Word file for testing - with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_docx: - try: - # Create a simple Word document content - tmp_docx.write(b'PK\x03\x04') # Basic DOCX header - tmp_docx_path = tmp_docx.name - - # Test the Word extraction - text = extract_text_from_word(tmp_docx_path) - print(f"✓ Word extraction completed. Text length: {len(text)}") - - # Clean up - os.unlink(tmp_docx_path) - - except Exception as e: - print(f"✗ Word extraction failed: {e}") - # Clean up on failure - if os.path.exists(tmp_docx.name): - os.unlink(tmp_docx.name) - - except Exception as e: - print(f"✗ Word extraction setup failed: {e}") - -def test_unified_document_parser(): - """Test the unified document parser""" - print("\n--- Testing Unified Document Parser ---") - - # Test with non-existent file - try: - extract_text_from_document('/nonexistent/file.pdf') - print("✗ Should have failed for non-existent file") - except FileNotFoundError: - print("✓ Correctly handled non-existent file") - except Exception as e: - print(f"✗ Unexpected error for non-existent file: {e}") - - # Test with unsupported file type - with tempfile.NamedTemporaryFile(suffix='.txt', delete=False) as tmp_txt: - try: - tmp_txt.write(b'This is a text file') - tmp_txt_path = tmp_txt.name - - try: - extract_text_from_document(tmp_txt_path) - print("✗ Should have failed for unsupported file type") - except ValueError as e: - print(f"✓ Correctly handled unsupported file type: {e}") - except Exception as e: - print(f"✗ Unexpected error for unsupported file type: {e}") - - # Clean up - os.unlink(tmp_txt_path) - - except Exception as e: - print(f"✗ Test setup failed: {e}") - -def main(): - """Run all tests""" - print("Starting Word Document Integration Tests...") - - test_pdf_extraction() - test_word_extraction() - test_unified_document_parser() - - print("\n--- Test Summary ---") - print("Integration tests completed. Check the output above for any errors.") - print("\nNote: For full Word document processing, ensure python-docx is installed:") - print("pip install python-docx") - -if __name__ == "__main__": - main() diff --git a/txt b/txt deleted file mode 100644 index c55b1e9..0000000 --- a/txt +++ /dev/null @@ -1 +0,0 @@ -Please Indicate the area that you are interested in the CV (ICU, OR, Medical Ward, Surgical Ward, Women`s Health). Bachelor’s degree in Nursing with minimum GPA (4 out of 5). SCFHS Nurse license classified as (Specialist). Country Specialist Nurse license. BLS Certified (ACLS, PALS, NALS dependent on unit requirement). Home Country equivalent Board Certifications must be maintained when appropriate. 2 year or more experience. Experience in same area. Key Accountabilities & Responsibilities Clinical: Is accountable for utilizing the nursing process, appropriate tools, evidence based knowledge, empathy and compassion in the assessment, planning, implementation and evaluation of nursing care for individual patients, while considering the individuals holistic needs and in accordance with scope of practice, hospital policy and procedure. Demonstrates the knowledge and skills, including critical thinking, necessary to implement the nursing plan of care, nursing interventions and procedures, as necessary for the care of the individual patient taking into consideration the patient’s condition, culture and medical plan of care. Advocates for patient by guiding best practice and standards of care within multidisciplinary team. Reports any deterioration in patient’s condition to physician, escalating via chain of command as necessary. Works towards reducing length of stay by advocating commencing discharge planning on admission. Is an advocate for patient safety, minimizes patient risk by ensuring a safe environment and conditions. Follows all policies and procedures, reporting observed or perceived risks in a timely fashion Suggests opportunities for improvement in care and in care environment. Uses transcultural awareness during all interactions with patients, families and colleagues. Works effectively with multidisciplinary team towards safe, effective and efficient patient outcomes. Maintains patient confidentiality according to policy. Ensures appropriate delegation of any duties when necessary. Takes appropriate action in emergencies. Documents all care accurately, completely and in a timely fashion. Demonstrates the computer skills necessary for carrying out duties. Education: Educates and informs patients and families on the care and management of condition, to adapt a problem-solving approach to managing and reporting complications. Provides health promotion and disease prevention advice. Participates in own and others education, training and professional development as needed. Shares appropriate, relevant and up to date information and knowledge with colleagues. Leadership: Utilizes leadership skills as nurse in charge of shift. Communicates effectively to ensure patient safety. Demonstrates knowledge of and follows all hospital related policies and procedures Reports potential or observed safety hazards immediately to supervisor, removes hazard or provides solution when possible. Reports any work-related risks such as equipment failures or resource insufficiencies to supervisor immediately. Uses communication and conflict negotiation skills to prevent and resolve complaints, reports through the chain of command as per hospital policy and procedure. Contributes to improving quality outcomes including patient and staff satisfaction indicators. Utilizes multidisciplinary team to ensure optimal patient experience. Exercises care in utilizing resources to maintain a cost efficient service. Actively participates in unit based meetings and councils. Supports a just culture, speaks up and promotes civility in the workplace. Research: Identifies opportunities for improvement that are evidence based. Maintains and shares up to date knowledge related to evidence based practice. Is involved in relevant projects related to practice, improvement and safety. Is actively involved in unit journal club. Hospital-wide: Respects patients and their families and promotes a patient-centered care culture. Participates and supports quality improvement and patient safety activities as an individual or as part of multidisciplinary teams. Performs other job-related duties as assigned. \ No newline at end of file