diff --git a/PRODUCTION_SETUP.md b/PRODUCTION_SETUP.md new file mode 100644 index 0000000..424aa26 --- /dev/null +++ b/PRODUCTION_SETUP.md @@ -0,0 +1,1011 @@ +# Production Setup Guide for University ATS + +This guide covers the complete setup for deploying the University ATS application to production, with special focus on frontend asset optimization. + +## Table of Contents + +1. [Frontend Asset Optimization](#frontend-asset-optimization) +2. [Django Production Configuration](#django-production-configuration) +3. [Static & Media Files](#static--media-files) +4. [Performance Optimization](#performance-optimization) +5. [Security Configuration](#security-configuration) +6. [Deployment Setup](#deployment-setup) +7. [Additional Services](#additional-services) + +--- + +## Frontend Asset Optimization + +### Current Setup (Development) + +Your `base.html` currently uses CDN links for: + +```html + + + +``` + +**This is NOT suitable for production** because: +- Depends on external CDNs (reliability & privacy concerns) +- Slower initial page load +- No optimization/minification control +- Potential CSP violations +- No offline capability + +--- + +### 1. Tailwind CSS Production Setup + +#### Step 1: Install Tailwind CSS Locally + +```bash +npm install -D tailwindcss postcss autoprefixer +``` + +#### Step 2: Create Tailwind Configuration + +Create `kaauh_ats/tailwind.config.js`: + +```javascript +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + "./kaauh_ats/templates/**/*.html", + "./kaauh_ats/templates/**/*.django", + "./kaauh_ats/static/**/*.html", + ], + theme: { + extend: { + colors: { + 'temple-red': '#9d2235', + 'temple-dark': '#1a1a1a', + 'temple-cream': '#f8f7f2', + 'dashboard-blue': '#4e73df', + } + } + }, + plugins: [], +} +``` + +#### Step 3: Create PostCSS Configuration + +Create `kaauh_ats/postcss.config.js`: + +```javascript +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} +``` + +#### Step 4: Create Main CSS File + +Create `kaauh_ats/static/css/styles.css`: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +#### Step 5: Build Tailwind CSS for Production + +Add to `kaauh_ats/package.json` scripts: + +```json +{ + "scripts": { + "build:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.min.css --minify", + "watch:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.css --watch" + } +} +``` + +Build command: +```bash +npm run build:css +``` + +#### Step 6: Update base.html Template + +**Replace:** +```html + + +``` + +**With:** +```html +{% load static %} + +``` + +--- + +### 2. Lucide Icons Production Setup + +#### Step 1: Install Lucide Icons Locally + +```bash +npm install lucide +``` + +#### Step 2: Create Icons Bundle Script + +Create `kaauh_ats/static/js/build-icons.js`: + +```javascript +// Import only the icons you use (tree-shaking) +import { + createIcons, + LayoutGrid, + Briefcase, + Building2, + Users, + User, + Calendar, + Mail, + Globe, + Settings, + LogOut, + Menu, + PanelLeft, + Maximize, + Minimize, + X, + Tag, + Clock, + Timer, + Hash, + Lock, + Link, + DoorOpen, + MapPin, + Video, + CheckCircle, + AlertCircle, + AlertTriangle, + Info, + Save +} from 'lucide'; + +// Register icons globally +createIcons({ + icons: { + 'layout-grid': LayoutGrid, + 'briefcase': Briefcase, + 'building-2': Building2, + 'users': Users, + 'user': User, + 'calendar': Calendar, + 'mail': Mail, + 'globe': Globe, + 'settings': Settings, + 'log-out': LogOut, + 'menu': Menu, + 'panel-left': PanelLeft, + 'maximize': Maximize, + 'minimize': Minimize, + 'x': X, + 'tag': Tag, + 'clock': Clock, + 'timer': Timer, + 'hash': Hash, + 'lock': Lock, + 'link': Link, + 'door-open': DoorOpen, + 'map-pin': MapPin, + 'video': Video, + 'check-circle': CheckCircle, + 'alert-circle': AlertCircle, + 'alert-triangle': AlertTriangle, + 'info': Info, + 'save': Save + } +}); +``` + +#### Step 3: Build Icons Bundle + +Add to `package.json`: + +```json +{ + "scripts": { + "build:icons": "esbuild kaauh_ats/static/js/build-icons.js --bundle --outfile=kaauh_ats/static/js/icons.min.js --minify" + } +} +``` + +Install esbuild: +```bash +npm install -D esbuild +``` + +Build: +```bash +npm run build:icons +``` + +#### Step 4: Update base.html Template + +**Replace:** +```html + +``` + +**With:** +```html +{% load static %} + +``` + +--- + +### 3. HTMX Production Setup + +#### Step 1: Install HTMX Locally + +```bash +npm install htmx.org +``` + +#### Step 2: Copy HTMX to Static Files + +```bash +cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/ +``` + +Or add to build script: +```json +{ + "scripts": { + "copy:htmx": "cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/" + } +} +``` + +#### Step 3: Update base.html Template + +**Replace:** +```html + +``` + +**With:** +```html +{% load static %} + +``` + +--- + +### 4. Combined Build Process + +Update `package.json` with all build commands: + +```json +{ + "name": "kaauh-ats", + "version": "1.0.0", + "scripts": { + "build": "npm run build:css && npm run build:icons && npm run copy:htmx", + "build:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.min.css --minify", + "build:icons": "esbuild kaauh_ats/static/js/build-icons.js --bundle --outfile=kaauh_ats/static/js/icons.min.js --minify", + "copy:htmx": "cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/", + "watch": "npm run watch:css", + "watch:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.css --watch" + }, + "devDependencies": { + "autoprefixer": "^10.4.17", + "esbuild": "^0.19.11", + "htmx.org": "^1.9.10", + "lucide": "^0.294.0", + "postcss": "^8.4.33", + "tailwindcss": "^3.4.1" + } +} +``` + +Install all dependencies: +```bash +npm install +``` + +Build everything: +```bash +npm run build +``` + +--- + +## Django Production Configuration + +### 1. Update Settings + +Create `kaauh_ats/NorahUniversity/settings_production.py`: + +```python +from .settings import * + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = [ + 'yourdomain.com', + 'www.yourdomain.com', + 'api.yourdomain.com', +] + +# Email Configuration +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.gmail.com') +EMAIL_PORT = int(os.environ.get('EMAIL_PORT', '587')) +EMAIL_USE_TLS = True +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER') +EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD') +DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'noreply@yourdomain.com') + +# Database Configuration +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': os.environ.get('DB_NAME'), + 'USER': os.environ.get('DB_USER'), + 'PASSWORD': os.environ.get('DB_PASSWORD'), + 'HOST': os.environ.get('DB_HOST', 'localhost'), + 'PORT': os.environ.get('DB_PORT', '5432'), + 'CONN_MAX_AGE': 600, + 'OPTIONS': { + 'sslmode': 'require', + } + } +} + +# Static Files +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' + +# Media Files +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +# Security Settings +SECURE_SSL_REDIRECT = True +SECURE_HSTS_SECONDS = 31536000 +SECURE_HSTS_INCLUDE_SUBDOMAINS = True +SECURE_HSTS_PRELOAD = True +SESSION_COOKIE_SECURE = True +CSRF_COOKIE_SECURE = True +SECURE_BROWSER_XSS_FILTER = True +SECURE_CONTENT_TYPE_NOSNIFF = True +X_FRAME_OPTIONS = 'DENY' +SECURE_PROXY_SSL_HEADER = 'HTTP_X_FORWARDED_PROTO' + +# Redis Configuration +CACHES = { + 'default': { + 'BACKEND': 'django_redis.cache.RedisCache', + 'LOCATION': f"redis://{os.environ.get('REDIS_HOST', '127.0.0.1')}:{os.environ.get('REDIS_PORT', '6379')}/1", + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + } + } +} + +# Celery Configuration +CELERY_BROKER_URL = f"redis://{os.environ.get('REDIS_HOST', '127.0.0.1')}:{os.environ.get('REDIS_PORT', '6379')}/0" +CELERY_RESULT_BACKEND = 'django-db' + +# Logging +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', + }, + }, + 'handlers': { + 'file': { + 'level': 'INFO', + 'class': 'logging.FileHandler', + 'filename': '/var/log/django/django.log', + 'formatter': 'verbose', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file'], + 'level': 'INFO', + 'propagate': True, + }, + }, +} +``` + +### 2. Environment Variables + +Create `.env` file (do NOT commit to Git): + +```bash +# Django +DJANGO_SECRET_KEY=your-super-secret-key-change-this +DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com + +# Database +DB_NAME=your_database_name +DB_USER=your_database_user +DB_PASSWORD=your_database_password +DB_HOST=localhost +DB_PORT=5432 + +# Redis +REDIS_HOST=localhost +REDIS_PORT=6379 + +# Email +EMAIL_HOST=smtp.gmail.com +EMAIL_PORT=587 +EMAIL_HOST_USER=your-email@gmail.com +EMAIL_HOST_PASSWORD=your-app-password +DEFAULT_FROM_EMAIL=noreply@yourdomain.com + +# Social Login (LinkedIn) +LINKEDIN_CLIENT_ID=your-linkedin-client-id +LINKEDIN_CLIENT_SECRET=your-linkedin-client-secret + +# Zoom +ZOOM_API_KEY=your-zoom-api-key +ZOOM_API_SECRET=your-zoom-api-secret +ZOOM_WEBHOOK_API_KEY=your-webhook-api-key +``` + +### 3. Install Production Dependencies + +```bash +pip install python-decouple gunicorn psycopg2-binary django-storages[boto3] +``` + +--- + +## Static & Media Files + +### 1. Collect Static Files + +```bash +cd kaauh_ats +python manage.py collectstatic --noinput --clear +``` + +This creates optimized, versioned static files in `staticfiles/`. + +### 2. Configure Nginx for Static Files + +```nginx +server { + listen 80; + server_name yourdomain.com www.yourdomain.com; + + # Redirect HTTP to HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name yourdomain.com www.yourdomain.com; + + root /var/www/kaauh_ats; + + # SSL Configuration + ssl_certificate /etc/ssl/certs/yourdomain.com.crt; + ssl_certificate_key /etc/ssl/private/yourdomain.com.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # Static Files with Caching + location /static/ { + alias /var/www/kaauh_ats/staticfiles/; + expires 1y; + add_header Cache-Control "public, immutable"; + access_log off; + gzip_static on; + } + + # Media Files + location /media/ { + alias /var/www/kaauh_ats/media/; + expires 30d; + add_header Cache-Control "public"; + access_log off; + gzip_static on; + } + + # Django Application + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_redirect off; + } +} +``` + +### 3. Media Files with S3 (Optional) + +If using S3 for media storage: + +```python +# settings_production.py +import os + +DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + +AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID') +AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY') +AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME') +AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1') +AWS_S3_ENDPOINT_URL = os.environ.get('AWS_S3_ENDPOINT_URL') +AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'} +AWS_DEFAULT_ACL = None +``` + +--- + +## Performance Optimization + +### 1. Enable Gzip Compression + +In `settings_production.py`: + +```python +MIDDLEWARE = [ + 'django.middleware.gzip.GZipMiddleware', + # ... other middleware +] + +GZIP_CONTENT_TYPES = [ + 'text/plain', + 'text/css', + 'text/xml', + 'text/javascript', + 'application/javascript', + 'application/json', + 'application/xml', +] +``` + +### 2. Browser Caching + +```python +# Cache configuration +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.redis.RedisCache', + 'LOCATION': 'redis://127.0.0.1:6379/1', + 'OPTIONS': { + 'CLIENT_CLASS': 'django_redis.client.DefaultClient', + }, + 'TIMEOUT': 300, + 'OPTIONS': { + 'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor', + } + }, + 'staticfiles': { + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + 'LOCATION': '/var/cache/django/staticfiles/', + } +} + +# Static files storage with manifest for cache busting +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' +``` + +### 3. Connection Pooling + +```python +# Database connection pooling +DATABASES['default']['OPTIONS'] = { + 'MAX_CONNS': 20, + 'MAX_CONN_AGE': 0, + 'CONN_MAX_AGE': 600, +} +``` + +### 4. Whitenoise for Static Files (Alternative) + +Install Whitenoise: +```bash +pip install whitenoise[brotli] +``` + +Update `wsgi.py`: + +```python +from whitenoise.middleware import WhiteNoiseMiddleware +from django.core.wsgi import get_wsgi_application + +application = get_wsgi_application() +application = WhiteNoiseMiddleware(application, static_root='/var/www/kaauh_ats/staticfiles/') +``` + +--- + +## Security Configuration + +### 1. SSL/TLS Setup + +```bash +# Let's Encrypt (Free SSL) +sudo apt install certbot python3-certbot-nginx +sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com +``` + +Auto-renewal: +```bash +sudo certbot renew --dry-run +sudo systemctl status certbot.timer +``` + +### 2. Firewall Configuration + +```bash +# UFW Firewall +sudo ufw allow 22/tcp # SSH +sudo ufw allow 80/tcp # HTTP +sudo ufw allow 443/tcp # HTTPS +sudo ufw enable +``` + +### 3. Content Security Policy + +Add to `settings_production.py`: + +```python +CSP_DEFAULT_SRC = ("'self'", "https://cdn.tailwindcss.com") +CSP_IMG_SRC = ("'self'", "data:", "https:") +CSP_SCRIPT_SRC = ("'self'",) +CSP_STYLE_SRC = ("'self'", "'unsafe-inline'") +``` + +--- + +## Deployment Setup + +### 1. Gunicorn WSGI Server + +Install Gunicorn: +```bash +pip install gunicorn +``` + +Create Gunicorn systemd service: + +`/etc/systemd/system/gunicorn.service`: + +```ini +[Unit] +Description=gunicorn daemon for Kaauh ATS +After=network.target + +[Service] +User=www-data +Group=www-data +WorkingDirectory=/var/www/kaauh_ats +ExecStart=/var/www/kaauh_ats/venv/bin/gunicorn \ + --workers 3 \ + --bind unix:/var/www/kaauh_ats/gunicorn.sock \ + --access-logfile /var/log/gunicorn/access.log \ + --error-logfile /var/log/gunicorn/error.log \ + --log-level info \ + --timeout 120 \ + --keepalive 5 \ + --max-requests 1000 \ + --max-requests-jitter 100 \ + NorahUniversity.wsgi:application + +[Install] +WantedBy=multi-user.target +``` + +Start service: +```bash +sudo systemctl start gunicorn +sudo systemctl enable gunicorn +sudo systemctl status gunicorn +``` + +### 2. Celery Worker Service + +`/etc/systemd/system/celery.service`: + +```ini +[Unit] +Description=Celery Worker +After=network.target + +[Service] +Type=forking +User=www-data +Group=www-data +WorkingDirectory=/var/www/kaauh_ats +Environment="PATH=/var/www/kaauh_ats/venv/bin" +ExecStart=/var/www/kaauh_ats/venv/bin/celery -A NorahUniversity worker \ + --loglevel=info \ + --logfile=/var/log/celery/worker.log \ + --pidfile=/var/run/celery/worker.pid \ + --concurrency=4 + +[Install] +WantedBy=multi-user.target +``` + +`/etc/systemd/system/celerybeat.service`: + +```ini +[Unit] +Description=Celery Beat +After=network.target + +[Service] +Type=forking +User=www-data +Group=www-data +WorkingDirectory=/var/www/kaauh_ats +Environment="PATH=/var/www/kaauh_ats/venv/bin" +ExecStart=/var/www/kaauh_ats/venv/bin/celery -A NorahUniversity beat \ + --loglevel=info \ + --logfile=/var/log/celery/beat.log \ + --pidfile=/var/run/celery/beat.pid \ + --scheduler django_celery_beat.schedulers:DatabaseScheduler + +[Install] +WantedBy=multi-user.target +``` + +Start services: +```bash +sudo systemctl start celery celerybeat +sudo systemctl enable celery celerybeat +``` + +### 3. Redis Service + +```bash +sudo apt install redis-server +sudo systemctl start redis +sudo systemctl enable redis +``` + +### 4. PostgreSQL Setup + +```bash +sudo apt install postgresql postgresql-contrib +sudo -u postgres psql + +CREATE DATABASE kaauh_ats; +CREATE USER kaauh_ats_user WITH PASSWORD 'secure_password'; +GRANT ALL PRIVILEGES ON DATABASE kaauh_ats TO kaauh_ats_user; +\q +``` + +### 5. Deployment Script + +Create `deploy.sh`: + +```bash +#!/bin/bash +set -e + +echo "Starting deployment..." + +# Pull latest code +cd /var/www/kaauh_ats +git pull origin main + +# Activate virtual environment +source venv/bin/activate + +# Install dependencies +pip install -r requirements.txt + +# Install and build frontend assets +npm install +npm run build + +# Run migrations +python manage.py migrate --noinput + +# Collect static files +python manage.py collectstatic --noinput --clear + +# Restart services +sudo systemctl restart gunicorn +sudo systemctl restart celery +sudo systemctl restart celerybeat + +echo "Deployment completed successfully!" +``` + +Make executable: +```bash +chmod +x deploy.sh +``` + +--- + +## Additional Services + +### 1. Monitoring & Logging + +Install Sentry for error tracking: + +```bash +pip install sentry-sdk[django] +``` + +Configure in `settings_production.py`: + +```python +import sentry_sdk +from sentry_sdk.integrations.django import DjangoIntegration + +sentry_sdk.init( + dsn=os.environ.get('SENTRY_DSN'), + integrations=[DjangoIntegration()], + traces_sample_rate=1.0, + profiles_sample_rate=1.0, +) +``` + +### 2. Backup Strategy + +Create backup script `backup.sh`: + +```bash +#!/bin/bash +BACKUP_DIR="/var/backups/kaauh-ats" +DATE=$(date +%Y%m%d_%H%M%S) + +# Backup Database +pg_dump -U kaauh_ats_user kaauh_ats > $BACKUP_DIR/db_$DATE.sql + +# Backup Media Files +tar -czf $BACKUP_DIR/media_$DATE.tar.gz /var/www/kaauh_ats/media/ + +# Delete backups older than 30 days +find $BACKUP_DIR -type f -mtime +30 -delete +``` + +### 3. Health Checks + +Create health check endpoint in `urls.py`: + +```python +from django.http import JsonResponse + +def health_check(request): + try: + # Check database + from django.db import connections + connections['default'].cursor() + + # Check Redis + from django.core.cache import cache + cache.set('health_check', 'ok', 10) + cache.get('health_check') + + return JsonResponse({'status': 'healthy'}) + except Exception as e: + return JsonResponse({'status': 'unhealthy', 'error': str(e)}, status=503) +``` + +--- + +## Pre-Deployment Checklist + +- [ ] All CDNs replaced with local assets +- [ ] Tailwind CSS built and minified +- [ ] Lucide icons bundled and minified +- [ ] HTMX downloaded and minified +- [ ] DEBUG = False in production settings +- [ ] SECRET_KEY changed from development +- [ ] ALLOWED_HOSTS configured +- [ ] Database connection configured +- [ ] Redis connection configured +- [ ] Static files collected +- [ ] SSL/TLS certificates installed +- [ ] Firewall configured +- [ ] Gunicorn service running +- [ ] Celery workers running +- [ ] Redis service running +- [ ] Nginx configured +- [ ] Environment variables set +- [ ] Email backend configured +- [ ] Logging configured +- [ ] Monitoring setup (Sentry) +- [ ] Backup strategy in place +- [ ] Health check endpoint accessible +- [ ] All tests passing + +--- + +## Troubleshooting + +### Issue: Static files not loading + +**Solution:** +```bash +python manage.py collectstatic --clear --noinput +sudo systemctl reload nginx +``` + +### Issue: Permission denied errors + +**Solution:** +```bash +sudo chown -R www-data:www-data /var/www/kaauh_ats +sudo chmod -R 755 /var/www/kaauh_ats +``` + +### Issue: Database connection errors + +**Solution:** +```bash +# Check PostgreSQL is running +sudo systemctl status postgresql + +# Check connection settings in .env +# Test connection +psql -h localhost -U kaauh_ats_user -d kaauh_ats +``` + +### Issue: Celery tasks not executing + +**Solution:** +```bash +# Check Celery logs +sudo tail -f /var/log/celery/worker.log + +# Restart Celery +sudo systemctl restart celery +``` + +--- + +## Resources + +- [Django Production Deployment](https://docs.djangoproject.com/en/stable/howto/deployment/) +- [Tailwind CSS Documentation](https://tailwindcss.com/docs/installation) +- [Gunicorn Documentation](https://docs.gunicorn.org/) +- [Nginx Documentation](https://nginx.org/en/docs/) +- [Let's Encrypt](https://letsencrypt.org/) + +--- + +**Last Updated:** February 1, 2026 +**Version:** 1.0.0 \ No newline at end of file diff --git a/recruitment/models.py b/recruitment/models.py index c847ff8..ea29348 100644 --- a/recruitment/models.py +++ b/recruitment/models.py @@ -1047,7 +1047,11 @@ class Application(Base): def get_meetings(self): """Legacy compatibility - get scheduled interviews for this application""" return self.scheduled_interviews.all() - + + @property + def get_latest_meeting(self): + """Legacy compatibility - get latest scheduled interview for this application""" + return self.scheduled_interviews.order_by('-interview_date', '-interview_time').first() @property def has_future_meeting(self): """Legacy compatibility - check for future meetings""" diff --git a/recruitment/templatetags/logo_tags.py b/recruitment/templatetags/logo_tags.py index a9cd5d8..6ca2079 100644 --- a/recruitment/templatetags/logo_tags.py +++ b/recruitment/templatetags/logo_tags.py @@ -6,74 +6,90 @@ register = template.Library() @register.simple_tag def logo_tenhal_ats(height="40", width=None): """ - Renders the Tenhal ATS logo SVG. + Renders Tenhal ATS full logo SVG (icon + text). + Optimized for all screen sizes. Usage: {% logo_tenhal_ats height="40" %} """ # Calculate width proportionally if not provided if width is None: - width = str(int(float(height) * 3.57)) # Aspect ratio of 500/140 + width = str(int(float(height) * 4.2)) # Adjusted aspect ratio svg = f''' - - - - {% trans "Email Addresses" %} - - - - - - - - - -
-
- -
-
- -
-

{% trans "Email Addresses" %}

-

- {% trans "These email addresses are linked to your account. You can set primary address, resend verification, or remove an address." %} -

+{% block title %}{% trans "Email Addresses" %} - {{ block.super }}{% endblock %} + +{% block content %} +
+ + + + + +
+

+
+
+ {% trans "Email Addresses" %} +

+
+ +
+
+
+ + {% trans "About Email Addresses" %} +
+

+ {% trans "These email addresses are linked to your account. You can set primary address, resend verification, or remove an address." %} +

+
+
+ + +
+
+

+ + {% trans "Linked Email Addresses" %} +

+
+ +
{% if messages %} {% for message in messages %} - - - - \ No newline at end of file + } + + // Form validation + const form = document.getElementById('add-email-form'); + if (form) { + form.addEventListener('submit', function(e) { + const emailInput = document.getElementById('id_email'); + const emailValue = emailInput.value.trim(); + + if (!emailValue) { + e.preventDefault(); + alert("{% trans 'Please enter an email address.' %}"); + emailInput.focus(); + return false; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(emailValue)) { + e.preventDefault(); + alert("{% trans 'Please enter a valid email address.' %}"); + emailInput.focus(); + return false; + } + }); + } +}); + +{% endblock %} \ No newline at end of file diff --git a/templates/account/password_change.html b/templates/account/password_change.html index fcb83a9..6bf97b2 100644 --- a/templates/account/password_change.html +++ b/templates/account/password_change.html @@ -1,98 +1,137 @@ -{% load static %} +{% extends "base.html" %} {% load i18n %} -{% get_current_language_bidi as LANGUAGE_BIDI %} -{% get_current_language as LANGUAGE_CODE %} - - - - - - {% trans "Change Password" %} - KAAUH ATS - - - - - - - - - -
-
- -
-
- +{% block title %}{% trans "Change Password" %} - {{ block.super }}{% endblock %} + +{% block content %} +
+ + + + + +
+
+

+ + {% trans "Change Password" %} +

+
+ +
+
+
+
-

{% trans "Change Password" %}

-

+

{% trans "Please enter your current password and a new password to secure your account." %}

-
+ {% csrf_token %} {% if form.non_field_errors %} -
- - - \ No newline at end of file + } +}); + +{% endblock %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index b2301b8..0fa04cc 100644 --- a/templates/base.html +++ b/templates/base.html @@ -273,12 +273,12 @@