- Updated features list with navigation bar, HR notifications, and security features - Added navigation and HR features sections with usage examples - Updated file structure to show modular app/ package - Changed running instructions from app.py to run.py - Added mailcatcher as default SMTP configuration - Included testing instructions and architecture documentation - Updated data.yaml example with status and submitted_at fields - Added comprehensive Security Features section (CSRF, rate limiting) - Added Architecture section explaining factory pattern and modular structure - Enhanced Troubleshooting section with mailcatcher, testing, and navigation issues - Updated technology stack with all dependencies and versions
547 lines
16 KiB
Markdown
547 lines
16 KiB
Markdown
# Job Application System
|
|
|
|
A professional, accessible Flask web application for job applications with email-based resume functionality, file uploads, and HR notifications.
|
|
|
|
## Features
|
|
|
|
- **Multi-step application process**: 5-page workflow with progress navigation
|
|
- **Progress navigation bar**: Visual indicator with backward navigation to edit previous pages
|
|
- **Email-based resume**: Applicants receive a unique link to resume their application
|
|
- **HR notifications**: Automated email alerts to HR when applications are submitted
|
|
- **Application review page**: Dedicated HR interface to view applications and download attachments
|
|
- **Stateful sessions**: Applications can be paused and resumed at any time
|
|
- **File uploads**: Support for CV, cover letter, and other documents (up to 3 files, 4 MB each)
|
|
- **German interface**: All user-facing content in German
|
|
- **File-based storage**: No database required - uses YAML for data serialization
|
|
- **Security features**: CSRF protection and rate limiting (5-second cooldown)
|
|
- **Modular architecture**: Clean separation of concerns with application factory pattern
|
|
- **Accessible design**: Minimal CSS, no JavaScript required, keyboard-friendly
|
|
- **Validation**: Comprehensive server-side validation for all inputs
|
|
- **Full test coverage**: 102 automated tests with 100% pass rate
|
|
|
|
## Application Workflow
|
|
|
|
1. **Email Capture**: User enters email and receives a resume link
|
|
2. **Personal Information**: Name, address, phone, birth year, etc.
|
|
3. **Motivation Questions**: Current job situation, motivation, qualifications, salary expectations
|
|
4. **Document Upload**: Upload up to 3 documents (CV, cover letter, etc.)
|
|
5. **Confirmation**: Success message with next steps
|
|
|
|
### Navigation Features
|
|
|
|
- **Progress bar**: Visual indicator shows current step and completion status
|
|
- **Backward navigation**: Users can click on completed steps to return and edit data
|
|
- **Sequential progression**: Users must complete pages in order (cannot skip ahead)
|
|
- **Data preservation**: All entered information is automatically saved when navigating
|
|
|
|
### HR Features
|
|
|
|
- **Email notifications**: HR receives an email when an application is submitted
|
|
- **Review interface**: Access applications at `/application/{session-id}/`
|
|
- **Download attachments**: HR can download all uploaded documents
|
|
- **Complete data view**: See all applicant information in one place
|
|
|
|
## Technology Stack
|
|
|
|
- **Python** 3.8+
|
|
- **Flask** 3.0.0 (with application factory pattern)
|
|
- **Flask-WTF** 1.2.1 (CSRF protection)
|
|
- **Flask-Mail** 0.9.1 (email sending)
|
|
- **PyYAML** 6.0.1 (data serialization)
|
|
- **Werkzeug** 3.0.1 (file handling)
|
|
- **pytest** 7.4.3 (testing framework)
|
|
- Minimal CSS for styling
|
|
- No JavaScript required
|
|
|
|
## Installation
|
|
|
|
### Prerequisites
|
|
|
|
- Python 3.8 or higher
|
|
- pip (Python package manager)
|
|
|
|
### Setup Steps
|
|
|
|
1. **Clone or download this repository**
|
|
|
|
2. **Create a virtual environment** (recommended):
|
|
```bash
|
|
python -m venv venv
|
|
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
```
|
|
|
|
3. **Install dependencies**:
|
|
```bash
|
|
pip install -r requirements.txt
|
|
```
|
|
|
|
4. **Configure environment variables**:
|
|
- Copy `.env.example` to `.env`:
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
- Edit `.env` and configure your settings:
|
|
```
|
|
SECRET_KEY=your-secret-key-here
|
|
COMPANY_NAME=Your Company Name
|
|
HR_EMAIL=hr@example.com
|
|
APPLICATION_URL_BASE=http://localhost:5000
|
|
```
|
|
|
|
### Email Configuration
|
|
|
|
The application defaults to **mailcatcher** for development (localhost:1025, no authentication required).
|
|
|
|
#### For Development (Mailcatcher)
|
|
|
|
1. **Install mailcatcher**:
|
|
```bash
|
|
gem install mailcatcher
|
|
```
|
|
|
|
2. **Run mailcatcher**:
|
|
```bash
|
|
mailcatcher
|
|
```
|
|
|
|
3. **View emails**: Open http://localhost:1080 in your browser
|
|
|
|
4. **No .env changes needed** - the application uses mailcatcher by default
|
|
|
|
#### For Production (Real SMTP)
|
|
|
|
Add to your `.env` file:
|
|
|
|
```
|
|
MAIL_SERVER=smtp.gmail.com
|
|
MAIL_PORT=587
|
|
MAIL_USE_TLS=True
|
|
MAIL_USERNAME=your-email@example.com
|
|
MAIL_PASSWORD=your-app-password
|
|
```
|
|
|
|
For **Gmail**:
|
|
1. Enable 2-factor authentication on your Google account
|
|
2. Generate an "App Password" at https://myaccount.google.com/apppasswords
|
|
3. Use the app password (not your regular password) in `MAIL_PASSWORD`
|
|
|
|
For **other email providers**, adjust `MAIL_SERVER` and `MAIL_PORT` accordingly.
|
|
|
|
## Running the Application
|
|
|
|
### Development Mode
|
|
|
|
```bash
|
|
python run.py
|
|
```
|
|
|
|
Or using Flask CLI:
|
|
|
|
```bash
|
|
flask run
|
|
```
|
|
|
|
The application will run at `http://127.0.0.1:5000`
|
|
|
|
### Running Tests
|
|
|
|
The application includes a comprehensive test suite with 102 tests:
|
|
|
|
```bash
|
|
# Run all tests
|
|
pytest tests/ -v
|
|
|
|
# Run with coverage report
|
|
pytest tests/ --cov=app --cov-report=html
|
|
|
|
# Run specific test file
|
|
pytest tests/test_routes.py -v
|
|
```
|
|
|
|
### Production Deployment
|
|
|
|
For production, use a WSGI server like Gunicorn:
|
|
|
|
1. **Install Gunicorn**:
|
|
```bash
|
|
pip install gunicorn
|
|
```
|
|
|
|
2. **Run with Gunicorn**:
|
|
```bash
|
|
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app()"
|
|
```
|
|
|
|
3. **Use a reverse proxy** (e.g., Nginx) in front of Gunicorn
|
|
|
|
4. **Update production settings** in `.env`:
|
|
```
|
|
WTF_CSRF_SSL_STRICT=True
|
|
SESSION_COOKIE_SECURE=True
|
|
MAIL_SERVER=your-production-smtp-server
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Linking from Job Postings
|
|
|
|
Link to the application with a job parameter:
|
|
|
|
```
|
|
https://your-domain.com/apply?job=JuniorMarketingAssistant
|
|
```
|
|
|
|
The `job` parameter will be displayed throughout the application and stored with the application data.
|
|
|
|
### Resume Functionality
|
|
|
|
When applicants enter their email, they receive a link like:
|
|
|
|
```
|
|
https://your-domain.com/resume/{session-id}
|
|
```
|
|
|
|
This link allows them to resume their application at any time.
|
|
|
|
## File Structure
|
|
|
|
```
|
|
.
|
|
├── run.py # Application entry point
|
|
├── config.py # Configuration settings
|
|
├── requirements.txt # Python dependencies
|
|
├── .env.example # Example environment variables
|
|
├── README.md # This file
|
|
├── app/ # Main application package
|
|
│ ├── __init__.py # Application factory
|
|
│ ├── routes.py # Route handlers (all endpoints)
|
|
│ ├── models.py # Data persistence layer
|
|
│ ├── validators.py # Input validation functions
|
|
│ ├── email_service.py # Email sending functions
|
|
│ └── utils.py # Utility functions
|
|
├── templates/ # HTML templates
|
|
│ ├── base.html # Base template with navigation
|
|
│ ├── _navigation.html # Progress bar component
|
|
│ ├── page1_email.html # Email capture page
|
|
│ ├── page2_personal.html # Personal information page
|
|
│ ├── page3_motivation.html # Motivation questions page
|
|
│ ├── page4_upload.html # Document upload page
|
|
│ ├── page5_confirmation.html # Confirmation page
|
|
│ └── application_view.html # HR application review page
|
|
├── static/ # Static files
|
|
│ └── style.css # Styles (including navigation)
|
|
├── tests/ # Test suite (102 tests)
|
|
│ ├── conftest.py # Test configuration and fixtures
|
|
│ ├── test_routes.py # Route handler tests
|
|
│ ├── test_validation.py # Validation function tests
|
|
│ ├── test_email.py # Email functionality tests
|
|
│ ├── test_storage.py # Data persistence tests
|
|
│ ├── test_uploads.py # File upload tests
|
|
│ ├── test_integration.py # End-to-end workflow tests
|
|
│ ├── test_hr_notifications.py # HR email notification tests
|
|
│ ├── test_application_view.py # Application review page tests
|
|
│ └── test_config.py # Configuration tests
|
|
└── applications/ # Application data (created automatically)
|
|
└── {session-id}/
|
|
├── data.yaml # Application data
|
|
└── attachments/ # Uploaded files
|
|
```
|
|
|
|
## Data Storage
|
|
|
|
Each application is stored in its own folder under `./applications/{session-id}/`:
|
|
|
|
- `data.yaml`: Application data (personal info, answers, metadata)
|
|
- `attachments/`: Uploaded documents
|
|
|
|
### Example data.yaml structure:
|
|
|
|
```yaml
|
|
session_id: "abc123-def456-..."
|
|
email: "applicant@example.com"
|
|
job_name: "Junior Marketing Assistant"
|
|
current_page: 5
|
|
status: "submitted"
|
|
created_at: "2025-01-15T10:30:00"
|
|
updated_at: "2025-01-15T11:45:00"
|
|
submitted_at: "2025-01-15T11:45:00"
|
|
personal_info:
|
|
name: "Müller"
|
|
firstname: "Anna"
|
|
address: "Hauptstrasse 123"
|
|
zip_code: "8001"
|
|
city: "Zürich"
|
|
phone: "+41 79 123 45 67"
|
|
birth_year: "1990"
|
|
civil_status: "ledig"
|
|
motivation_answers:
|
|
current_job: "..."
|
|
motivation: "..."
|
|
qualifications: "..."
|
|
salary: "..."
|
|
uploaded_files:
|
|
- original_name: "CV.pdf"
|
|
stored_name: "20250115_103000_CV.pdf"
|
|
uploaded_at: "2025-01-15T10:30:00"
|
|
size: 1048576
|
|
```
|
|
|
|
## Validation Rules
|
|
|
|
### Email (Page 1)
|
|
- Valid email format required
|
|
|
|
### Personal Information (Page 2)
|
|
- **Name**: Required, max 255 characters
|
|
- **Firstname**: Required, max 255 characters
|
|
- **Address**: Required, max 255 characters
|
|
- **ZIP Code**: Required, numeric, max 10 digits
|
|
- **City**: Required, max 255 characters
|
|
- **Phone**: Required, international format (e.g., +41 79 123 45 67)
|
|
- **Birth Year**: Required, 4 digits, between 1940 and 2010
|
|
- **Civil Status**: Optional, max 255 characters
|
|
|
|
### Motivation (Page 3)
|
|
- All fields optional
|
|
- Each textarea limited to 3000 characters (~1 A4 page)
|
|
|
|
### File Upload (Page 4)
|
|
- Maximum 3 files
|
|
- Maximum 4 MB per file
|
|
- Allowed formats: PDF, DOC, DOCX, TXT, JPG, JPEG, PNG
|
|
|
|
## Security Features
|
|
|
|
### CSRF Protection
|
|
|
|
All forms are protected against Cross-Site Request Forgery (CSRF) attacks using Flask-WTF:
|
|
|
|
- CSRF tokens required on all POST requests
|
|
- Tokens included automatically in all forms
|
|
- No token expiration (users can take time filling forms)
|
|
- Configurable for production HTTPS environments
|
|
|
|
### Rate Limiting
|
|
|
|
Built-in rate limiting prevents form spam and abuse:
|
|
|
|
- 5-second cooldown between form submissions (configurable)
|
|
- Session-based tracking
|
|
- Customizable in `config.py`: `RATE_LIMIT_SECONDS = 5`
|
|
|
|
### Data Security
|
|
|
|
- **Session IDs**: Cryptographically secure UUIDs (not guessable)
|
|
- **File uploads**: Validated for size, type, and sanitized filenames
|
|
- **Path traversal protection**: Secure filename handling prevents directory attacks
|
|
- **Server-side validation**: All input validated on the server
|
|
- **Environment variables**: Sensitive configuration kept in `.env` file
|
|
- **No SQL injection**: File-based storage eliminates SQL vulnerabilities
|
|
|
|
### Production Security Checklist
|
|
|
|
When deploying to production, update `.env`:
|
|
|
|
```
|
|
SECRET_KEY=<strong-random-secret-key>
|
|
WTF_CSRF_SSL_STRICT=True
|
|
SESSION_COOKIE_SECURE=True
|
|
SESSION_COOKIE_HTTPONLY=True
|
|
SESSION_COOKIE_SAMESITE=Lax
|
|
```
|
|
|
|
## HR Features
|
|
|
|
### Accessing Applications
|
|
|
|
HR staff can review applications using the direct URL:
|
|
|
|
```
|
|
https://your-domain.com/application/{session-id}/
|
|
```
|
|
|
|
The session ID is included in the HR notification email sent when an application is submitted.
|
|
|
|
### HR Notification Emails
|
|
|
|
Configure HR email notifications in `.env`:
|
|
|
|
```
|
|
HR_EMAIL=hr@example.com
|
|
APPLICATION_URL_BASE=https://your-domain.com
|
|
```
|
|
|
|
When an applicant submits their application (page 4), an email is automatically sent to the HR email address containing:
|
|
|
|
- Applicant name and email
|
|
- Position applied for
|
|
- Submission timestamp
|
|
- Number of uploaded documents
|
|
- Direct link to the application review page
|
|
|
|
### Application Review Page
|
|
|
|
The review page (`/application/{session-id}/`) displays:
|
|
|
|
- Complete applicant information (all form fields)
|
|
- All uploaded documents with download links
|
|
- Submission timestamp and metadata
|
|
- Professional formatting for easy review
|
|
|
|
### Downloading Attachments
|
|
|
|
HR can download individual files directly from the review page:
|
|
|
|
```
|
|
https://your-domain.com/application/{session-id}/download/{filename}
|
|
```
|
|
|
|
Security: Only files listed in the application's uploaded_files list can be downloaded (prevents unauthorized access).
|
|
|
|
## Customization
|
|
|
|
### Changing Text Limits
|
|
|
|
Edit `config.py`:
|
|
|
|
```python
|
|
MAX_STRING_LENGTH = 255
|
|
MAX_TEXT_AREA_LENGTH = 3000
|
|
MAX_FILE_SIZE = 4 * 1024 * 1024 # 4 MB
|
|
MAX_FILES = 3
|
|
```
|
|
|
|
### Changing Allowed File Types
|
|
|
|
Edit `config.py`:
|
|
|
|
```python
|
|
ALLOWED_EXTENSIONS = {'pdf', 'doc', 'docx', 'txt', 'jpg', 'jpeg', 'png'}
|
|
```
|
|
|
|
### Translating to Another Language
|
|
|
|
Edit all templates in `./templates/` - they contain German text that can be translated.
|
|
|
|
## Architecture
|
|
|
|
### Application Factory Pattern
|
|
|
|
The application uses Flask's application factory pattern for better testability and flexibility:
|
|
|
|
- `app/__init__.py` contains the `create_app()` function
|
|
- Extensions (Flask-Mail, CSRF) are initialized within the factory
|
|
- Multiple app instances can be created (useful for testing)
|
|
- Configuration can be passed as a parameter
|
|
|
|
### Modular Structure
|
|
|
|
Code is organized by responsibility:
|
|
|
|
- **routes.py** (473 lines): All route handlers and request processing
|
|
- **models.py** (41 lines): Data persistence layer (YAML operations)
|
|
- **validators.py** (48 lines): Input validation functions
|
|
- **email_service.py** (85 lines): Email sending logic
|
|
- **utils.py** (30 lines): Utility functions (rate limiting, etc.)
|
|
|
|
Benefits:
|
|
- Easy to test individual modules in isolation
|
|
- Clear separation of concerns
|
|
- Easier to navigate and maintain
|
|
- Supports team collaboration
|
|
|
|
### Testing Strategy
|
|
|
|
The application includes 102 automated tests organized by functionality:
|
|
|
|
- **test_routes.py**: Route handler behavior
|
|
- **test_validation.py**: Input validation logic
|
|
- **test_email.py**: Email sending functionality
|
|
- **test_storage.py**: YAML data persistence
|
|
- **test_uploads.py**: File upload handling
|
|
- **test_integration.py**: End-to-end workflows
|
|
- **test_hr_notifications.py**: HR email notifications
|
|
- **test_application_view.py**: Application review page
|
|
- **test_config.py**: Configuration settings
|
|
|
|
Run tests frequently during development to catch regressions early.
|
|
|
|
## Troubleshooting
|
|
|
|
### Email not sending in development
|
|
|
|
**Using Mailcatcher (recommended for development):**
|
|
|
|
1. Verify mailcatcher is running:
|
|
```bash
|
|
mailcatcher
|
|
```
|
|
|
|
2. Check mailcatcher web interface at http://localhost:1080
|
|
|
|
3. Verify application is configured for mailcatcher:
|
|
- Default settings work out of the box (localhost:1025)
|
|
- No `.env` email configuration needed for development
|
|
|
|
**Using real SMTP:**
|
|
|
|
- Check your email credentials in `.env`
|
|
- For Gmail, ensure you're using an app password, not your regular password
|
|
- Check that `MAIL_USE_TLS` is set correctly for your email provider
|
|
- Check application logs for error messages
|
|
|
|
### Tests failing
|
|
|
|
If tests fail after making changes:
|
|
|
|
```bash
|
|
# Run specific test file to identify the issue
|
|
pytest tests/test_routes.py -v
|
|
|
|
# Run with detailed output
|
|
pytest tests/ -vv
|
|
|
|
# Check test coverage
|
|
pytest tests/ --cov=app --cov-report=term-missing
|
|
```
|
|
|
|
Common issues:
|
|
- Import errors: Check module structure in `app/` package
|
|
- Template errors: Verify templates exist in `templates/` directory
|
|
- CSRF errors: Tests disable CSRF in `conftest.py`
|
|
|
|
### File upload errors
|
|
|
|
- Ensure the `./applications/` folder exists and is writable
|
|
- Check file size limits in your web server configuration
|
|
- Verify allowed file extensions in `config.py`
|
|
- Check that storage path is correct in config
|
|
|
|
### Session not resuming
|
|
|
|
- Check that the `./applications/{session-id}/` folder exists
|
|
- Verify that `data.yaml` is valid YAML format
|
|
- Check application logs for errors
|
|
- Ensure session ID in URL is correct (UUID format)
|
|
|
|
### Navigation not working
|
|
|
|
- Verify `current_page` is being passed to templates
|
|
- Check that navigation component is included in `base.html`
|
|
- Ensure session data contains valid `current_page` value
|
|
- Check browser console for CSS/styling issues
|
|
|
|
### Rate limiting preventing form submission
|
|
|
|
- Check `RATE_LIMIT_SECONDS` in `config.py` (default: 5 seconds)
|
|
- Wait the specified time between submissions
|
|
- For testing, set to 0 in `.env`: `RATE_LIMIT_SECONDS=0`
|
|
|
|
## License
|
|
|
|
This project is provided as-is for use in job application processes.
|
|
|
|
## Support
|
|
|
|
For issues or questions, please contact your system administrator.
|