189 lines
7.7 KiB
Python
189 lines
7.7 KiB
Python
# jobs/models.py
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.core.validators import URLValidator
|
|
from .validators import validate_image_size, validate_hash_tags
|
|
import uuid
|
|
import os
|
|
|
|
class JobPosting(models.Model):
|
|
# Basic Job Information
|
|
JOB_TYPES = [
|
|
('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'),
|
|
]
|
|
|
|
# Core Fields
|
|
title = models.CharField(max_length=200)
|
|
department = models.CharField(max_length=100, blank=True)
|
|
job_type = models.CharField(max_length=20, choices=JOB_TYPES, default='FULL_TIME')
|
|
workplace_type = models.CharField(max_length=20, choices=WORKPLACE_TYPES, default='ON_SITE')
|
|
|
|
|
|
# Location
|
|
location_city = models.CharField(max_length=100, blank=True)
|
|
location_state = models.CharField(max_length=100, blank=True)
|
|
location_country = models.CharField(max_length=100, default='United States')
|
|
|
|
# Job Details
|
|
description = models.TextField(help_text="Full job description including responsibilities and requirements")
|
|
qualifications = models.TextField(blank=True, help_text="Required qualifications and skills")
|
|
salary_range = models.CharField(max_length=200, blank=True, help_text="e.g., $60,000 - $80,000")
|
|
benefits = models.TextField(blank=True, help_text="Benefits offered")
|
|
|
|
|
|
# Application Information
|
|
application_url = models.URLField(validators=[URLValidator()], help_text="URL where candidates apply")
|
|
application_deadline = models.DateField(null=True, blank=True)
|
|
application_instructions = models.TextField(blank=True, help_text="Special instructions for applicants")
|
|
|
|
# Internal Tracking
|
|
internal_job_id = models.CharField(max_length=50, primary_key=True, editable=False)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
created_by = models.CharField(max_length=100, blank=True, help_text="Name of person who created this job")
|
|
|
|
# Status Fields
|
|
STATUS_CHOICES = [
|
|
('DRAFT', 'Draft'),
|
|
('ACTIVE', 'Active'),
|
|
('CLOSED', 'Closed'),
|
|
('ARCHIVED', 'Archived'),
|
|
]
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='DRAFT')
|
|
|
|
#hashtags for social media
|
|
hash_tags = models.CharField(max_length=200, blank=True, help_text="Comma-separated hashtags for linkedin post like #hiring,#jobopening",validators=[validate_hash_tags])
|
|
|
|
# LinkedIn Integration Fields
|
|
linkedin_post_id = models.CharField(max_length=200, blank=True, help_text="LinkedIn post ID after posting")
|
|
linkedin_post_url = models.URLField(blank=True, help_text="Direct URL to LinkedIn post")
|
|
posted_to_linkedin = models.BooleanField(default=False)
|
|
linkedin_post_status = models.CharField(max_length=50, blank=True, help_text="Status of LinkedIn posting")
|
|
linkedin_posted_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
# University Specific Fields
|
|
position_number = models.CharField(max_length=50, blank=True, help_text="University position number")
|
|
reporting_to = models.CharField(max_length=100, blank=True, help_text="Who this position reports to")
|
|
start_date = models.DateField(null=True, blank=True, help_text="Desired start date")
|
|
open_positions = models.PositiveIntegerField(default=1, help_text="Number of open positions for this job")
|
|
|
|
class Meta:
|
|
ordering = ['-created_at']
|
|
verbose_name = "Job Posting"
|
|
verbose_name_plural = "Job Postings"
|
|
|
|
def __str__(self):
|
|
return f"{self.title} - {self.get_status_display()}"
|
|
|
|
def save(self, *args, **kwargs):
|
|
# Generate unique internal job ID if not exists
|
|
if not self.internal_job_id:
|
|
prefix = "UNIV"
|
|
year = timezone.now().year
|
|
# Get next sequential number
|
|
last_job = JobPosting.objects.filter(
|
|
internal_job_id__startswith=f"{prefix}-{year}-"
|
|
).order_by('internal_job_id').last()
|
|
|
|
if last_job:
|
|
last_num = int(last_job.internal_job_id.split('-')[-1])
|
|
next_num = last_num + 1
|
|
else:
|
|
next_num = 1
|
|
|
|
self.internal_job_id = f"{prefix}-{year}-{next_num:04d}"
|
|
|
|
super().save(*args, **kwargs)
|
|
|
|
def get_location_display(self):
|
|
"""Return formatted location string"""
|
|
parts = []
|
|
if self.location_city:
|
|
parts.append(self.location_city)
|
|
if self.location_state:
|
|
parts.append(self.location_state)
|
|
if self.location_country and self.location_country != 'United States':
|
|
parts.append(self.location_country)
|
|
return ', '.join(parts) if parts else 'Not specified'
|
|
|
|
def is_expired(self):
|
|
"""Check if application deadline has passed"""
|
|
if self.application_deadline:
|
|
return self.application_deadline < timezone.now().date()
|
|
return False
|
|
|
|
|
|
|
|
def image_file_name(instance, filename):
|
|
ext = os.path.splitext(filename)[-1]
|
|
new_filename = f'{instance.id}{ext}'
|
|
return f'uploads/linkedinpost/images/{new_filename}'
|
|
|
|
|
|
class PostImageUpload(models.Model):
|
|
id = models.UUIDField(
|
|
primary_key=True,
|
|
default=uuid.uuid4,
|
|
editable=False,
|
|
unique=True
|
|
)
|
|
job_posting = models.ForeignKey(JobPosting, on_delete=models.CASCADE, related_name='files')
|
|
linkedinpost_image=models.ImageField(upload_to=image_file_name, blank=True, null=True,validators=[validate_image_size], help_text="Image file (Max size: 2MB). Accepted formats: .png, .jpg, .jpeg")
|
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
verbose_name = "Job File"
|
|
verbose_name_plural = "Job Files"
|
|
|
|
def __str__(self):
|
|
return f"File for {self.job_posting.title}-{self.job_posting.internal_job_id} - {self.file.name}"
|
|
|
|
# class JobApplication(models.Model):
|
|
# # Job reference
|
|
# job_posting = models.ForeignKey(JobPosting, on_delete=models.CASCADE,related_name='applications')
|
|
|
|
# # Personal Information
|
|
# first_name = models.CharField(max_length=100)
|
|
# last_name = models.CharField(max_length=100)
|
|
# email = models.EmailField()
|
|
# phone = models.CharField(max_length=20, blank=True)
|
|
|
|
# # Resume and Documents
|
|
# resume = models.FileField(upload_to='resumes/', help_text="PDF or Word document")
|
|
# cover_letter = models.FileField(upload_to='cover_letters/', blank=True)
|
|
|
|
# # Additional Information
|
|
# linkedin_profile = models.URLField(blank=True, help_text="Your LinkedIn profile URL")
|
|
# portfolio_url = models.URLField(blank=True, help_text="Portfolio or personal website")
|
|
# salary_expectations = models.CharField(max_length=100, blank=True)
|
|
# availability = models.CharField(max_length=100, blank=True)
|
|
|
|
# # Application Details
|
|
# applied_at = models.DateTimeField(auto_now_add=True)
|
|
# status = models.CharField(max_length=20, default='RECEIVED', choices=[
|
|
# ('RECEIVED', 'Received'),
|
|
# ('UNDER_REVIEW', 'Under Review'),
|
|
# ('INTERVIEW', 'Interview Scheduled'),
|
|
# ('OFFER', 'Offer Extended'),
|
|
# ('REJECTED', 'Rejected'),
|
|
# ('HIRED', 'Hired'),
|
|
# ])
|
|
# class Meta:
|
|
# ordering = ['-applied_at']
|
|
# verbose_name = "Job Application"
|
|
# verbose_name_plural = "Job Applications"
|
|
|
|
# def __str__(self):
|
|
# return f"{self.first_name} {self.last_name} - {self.job_posting.title}"
|