✨ feat(jobs): add models, serializers, views, and routes for Company, JobPosting, and JobApplication
This commit is contained in:
5 files changed
+425
-3
No files matched your search
+115
-1
@@ -1,3 +1,117 @@
|
||||
from django.contrib import admin
|
||||
from jobs.models import Company, JobPosting, JobApplication
|
||||
|
||||
# Register your models here.
|
||||
|
||||
class JobApplicationInline(admin.TabularInline):
|
||||
model = JobApplication
|
||||
extra = 0
|
||||
readonly_fields = ("applicant", "status", "applied_at", "last_updated")
|
||||
can_delete = False
|
||||
|
||||
|
||||
@admin.register(Company)
|
||||
class CompanyAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "industry", "size", "location", "created_at")
|
||||
search_fields = ("name", "industry", "location")
|
||||
list_filter = ("industry", "size", "location")
|
||||
readonly_fields = ("created_at",)
|
||||
|
||||
|
||||
@admin.register(JobPosting)
|
||||
class JobPostingAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"title",
|
||||
"company",
|
||||
"posted_by",
|
||||
"job_type",
|
||||
"experience_level",
|
||||
"location",
|
||||
"is_remote",
|
||||
"status",
|
||||
"featured",
|
||||
"view_count",
|
||||
"application_deadline",
|
||||
"created_at",
|
||||
)
|
||||
list_filter = (
|
||||
"job_type",
|
||||
"experience_level",
|
||||
"status",
|
||||
"featured",
|
||||
"is_remote",
|
||||
"created_at",
|
||||
"application_deadline",
|
||||
"expires_at",
|
||||
)
|
||||
search_fields = (
|
||||
"title",
|
||||
"company__name",
|
||||
"location",
|
||||
"skills_required",
|
||||
"posted_by__username",
|
||||
)
|
||||
date_hierarchy = "created_at"
|
||||
autocomplete_fields = ("company", "posted_by")
|
||||
inlines = [JobApplicationInline]
|
||||
readonly_fields = ("view_count", "created_at", "updated_at")
|
||||
|
||||
fieldsets = (
|
||||
(
|
||||
"Basic Info",
|
||||
{
|
||||
"fields": (
|
||||
"title",
|
||||
"company",
|
||||
"posted_by",
|
||||
"description",
|
||||
"requirements",
|
||||
"benefits",
|
||||
"skills_required",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Job Details",
|
||||
{
|
||||
"fields": (
|
||||
"job_type",
|
||||
"experience_level",
|
||||
"location",
|
||||
"is_remote",
|
||||
"salary_min",
|
||||
"salary_max",
|
||||
"salary_currency",
|
||||
)
|
||||
},
|
||||
),
|
||||
(
|
||||
"Status & Metadata",
|
||||
{
|
||||
"fields": (
|
||||
"status",
|
||||
"featured",
|
||||
"view_count",
|
||||
"application_deadline",
|
||||
"expires_at",
|
||||
"external_url",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(JobApplication)
|
||||
class JobApplicationAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
"job",
|
||||
"applicant",
|
||||
"status",
|
||||
"applied_at",
|
||||
"last_updated",
|
||||
)
|
||||
list_filter = ("status", "applied_at", "last_updated")
|
||||
search_fields = ("job__title", "applicant__username")
|
||||
autocomplete_fields = ("job", "applicant")
|
||||
readonly_fields = ("applied_at", "last_updated")
|
||||
@@ -1,3 +1,120 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
APPROVAL_STATUS_CHOICE = [
|
||||
("draft", "Draft"),
|
||||
("pending", "Pending Review"),
|
||||
("active", "Active"),
|
||||
("closed", "Closed"),
|
||||
("rejected", "Rejected"),
|
||||
]
|
||||
EXPERIENCE_LEVEL_CHOICES = [
|
||||
("entry", "Entry Level"),
|
||||
("mid", "Mid Level"),
|
||||
("senior", "Senior Level"),
|
||||
("executive", "Executive"),
|
||||
]
|
||||
|
||||
JOB_TYPE_CHOICES = [
|
||||
("full_time", "Full Time"),
|
||||
("part_time", "Part Time"),
|
||||
("contract", "Contract"),
|
||||
("internship", "Internship"),
|
||||
("freelance", "Freelance"),
|
||||
]
|
||||
|
||||
JOB_APPLICATION_STATUS = [
|
||||
("submitted", "Submitted"),
|
||||
("under_review", "Under Review"),
|
||||
("shortlisted", "Shortlisted"),
|
||||
("interview", "Interview"),
|
||||
("offered", "Offered"),
|
||||
("hired", "Hired"),
|
||||
("rejected", "Rejected"),
|
||||
("withdrawn", "Withdrawn"),
|
||||
]
|
||||
|
||||
|
||||
class Company(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
logo = models.ImageField(upload_to="companies/", null=True, blank=True)
|
||||
website = models.URLField(blank=True)
|
||||
description = models.TextField(blank=True)
|
||||
industry = models.CharField(max_length=100, blank=True)
|
||||
size = models.CharField(max_length=50, blank=True)
|
||||
location = models.CharField(max_length=100, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
self.name
|
||||
|
||||
|
||||
class JobPosting(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
title = models.CharField(max_length=200)
|
||||
company = models.ForeignKey(
|
||||
Company, on_delete=models.CASCADE, related_name="job_postings"
|
||||
)
|
||||
posted_by = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, related_name="posted_jobs"
|
||||
)
|
||||
description = models.TextField()
|
||||
requirements = models.TextField()
|
||||
benefits = models.TextField(blank=True)
|
||||
location = models.CharField(max_length=100)
|
||||
is_remote = models.BooleanField(default=False)
|
||||
job_type = models.CharField(
|
||||
max_length=20,
|
||||
choices=JOB_TYPE_CHOICES,
|
||||
)
|
||||
experience_level = models.CharField(max_length=20, choices=EXPERIENCE_LEVEL_CHOICES)
|
||||
salary_min = models.PositiveIntegerField(null=True, blank=True)
|
||||
salary_max = models.PositiveIntegerField(null=True, blank=True)
|
||||
salary_currency = models.CharField(max_length=3, default="USD")
|
||||
application_deadline = models.DateTimeField(null=True, blank=True)
|
||||
external_url = models.URLField(blank=True)
|
||||
skills_required = models.JSONField(default=list)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=APPROVAL_STATUS_CHOICE,
|
||||
default="pending",
|
||||
)
|
||||
featured = models.BooleanField(default=False)
|
||||
view_count = models.PositiveIntegerField(default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
expires_at = models.DateTimeField()
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["title"]),
|
||||
models.Index(fields=["company"]),
|
||||
models.Index(fields=["status"]),
|
||||
]
|
||||
|
||||
|
||||
class JobApplication(models.Model):
|
||||
job = models.ForeignKey(
|
||||
JobPosting, on_delete=models.CASCADE, related_name="applications"
|
||||
)
|
||||
applicant = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, related_name="job_applications"
|
||||
)
|
||||
cover_letter = models.TextField()
|
||||
resume = models.FileField(upload_to="resumes/")
|
||||
additional_documents = models.JSONField(default=list)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=JOB_APPLICATION_STATUS,
|
||||
default="submitted",
|
||||
)
|
||||
applied_at = models.DateTimeField(auto_now_add=True)
|
||||
last_updated = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["job", "applicant"]
|
||||
@@ -0,0 +1,119 @@
|
||||
from rest_framework import serializers
|
||||
from jobs.models import Company, JobPosting, JobApplication
|
||||
from apps.users.models import User
|
||||
|
||||
|
||||
class CompanySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Company
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"logo",
|
||||
"website",
|
||||
"description",
|
||||
"industry",
|
||||
"size",
|
||||
"location",
|
||||
"created_at",
|
||||
]
|
||||
read_only_fields = ["id", "created_at"]
|
||||
|
||||
|
||||
class PosterSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "username", "email"]
|
||||
|
||||
|
||||
class JobPostingSerializer(serializers.ModelSerializer):
|
||||
company = CompanySerializer(read_only=True)
|
||||
company_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=Company.objects.all(), source="company", write_only=True
|
||||
)
|
||||
posted_by = PosterSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = JobPosting
|
||||
fields = [
|
||||
"id",
|
||||
"title",
|
||||
"company",
|
||||
"company_id",
|
||||
"posted_by",
|
||||
"description",
|
||||
"requirements",
|
||||
"benefits",
|
||||
"location",
|
||||
"is_remote",
|
||||
"job_type",
|
||||
"experience_level",
|
||||
"salary_min",
|
||||
"salary_max",
|
||||
"salary_currency",
|
||||
"application_deadline",
|
||||
"external_url",
|
||||
"skills_required",
|
||||
"status",
|
||||
"featured",
|
||||
"view_count",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"expires_at",
|
||||
]
|
||||
read_only_fields = ["id", "created_at", "updated_at", "view_count"]
|
||||
|
||||
|
||||
class JobPostingListSerializer(serializers.ModelSerializer):
|
||||
company = CompanySerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = JobPosting
|
||||
fields = [
|
||||
"id",
|
||||
"title",
|
||||
"company",
|
||||
"location",
|
||||
"is_remote",
|
||||
"job_type",
|
||||
"experience_level",
|
||||
"salary_min",
|
||||
"salary_max",
|
||||
"status",
|
||||
"featured",
|
||||
"expires_at",
|
||||
]
|
||||
|
||||
|
||||
class ApplicantSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["id", "username", "email"]
|
||||
|
||||
|
||||
class JobApplicationSerializer(serializers.ModelSerializer):
|
||||
applicant = ApplicantSerializer(read_only=True)
|
||||
applicant_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=User.objects.all(), source="applicant", write_only=True, required=False
|
||||
)
|
||||
job = JobPostingSerializer(read_only=True)
|
||||
job_id = serializers.PrimaryKeyRelatedField(
|
||||
queryset=JobPosting.objects.all(), source="job", write_only=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = JobApplication
|
||||
fields = [
|
||||
"id",
|
||||
"job",
|
||||
"job_id",
|
||||
"applicant",
|
||||
"applicant_id",
|
||||
"cover_letter",
|
||||
"resume",
|
||||
"additional_documents",
|
||||
"status",
|
||||
"applied_at",
|
||||
"last_updated",
|
||||
]
|
||||
read_only_fields = ["id", "applied_at", "last_updated", "status"]
|
||||
@@ -0,0 +1,9 @@
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from jobs.views import CompanyViewSet, JobPostingViewSet, JobApplicationViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"companies", CompanyViewSet)
|
||||
router.register(r"jobs", JobPostingViewSet)
|
||||
router.register(r"applications", JobApplicationViewSet)
|
||||
|
||||
urlpatterns = router.urls
|
||||
+65
-2
@@ -1,3 +1,66 @@
|
||||
from django.shortcuts import render
|
||||
from jobs.models import Company, JobApplication, JobPosting
|
||||
from jobs.serializers import (
|
||||
CompanySerializer,
|
||||
JobApplicationSerializer,
|
||||
JobPostingListSerializer,
|
||||
JobPostingSerializer,
|
||||
)
|
||||
from rest_framework import permissions, viewsets
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
# Create your views here.
|
||||
|
||||
class CompanyViewSet(viewsets.ModelViewSet):
|
||||
queryset = Company.objects.all()
|
||||
serializer_class = CompanySerializer
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save()
|
||||
|
||||
|
||||
class JobPostingViewSet(viewsets.ModelViewSet):
|
||||
queryset = JobPosting.objects.all().select_related("company", "posted_by")
|
||||
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
|
||||
|
||||
def get_serializer_class(self):
|
||||
if self.action == "list":
|
||||
return JobPostingListSerializer
|
||||
return JobPostingSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(posted_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
if serializer.instance.posted_by != self.request.user:
|
||||
raise PermissionDenied("You can only update jobs you've posted.")
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.posted_by != self.request.user:
|
||||
raise PermissionDenied("You can only delete jobs you've posted.")
|
||||
instance.delete()
|
||||
|
||||
|
||||
class JobApplicationViewSet(viewsets.ModelViewSet):
|
||||
queryset = JobApplication.objects.select_related("job", "applicant").all()
|
||||
serializer_class = JobApplicationSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def perform_create(self, serializer):
|
||||
job = serializer.validated_data["job"]
|
||||
applicant = self.request.user
|
||||
|
||||
if JobApplication.objects.filter(job=job, applicant=applicant).exists():
|
||||
raise PermissionDenied("You have already applied for this job.")
|
||||
|
||||
serializer.save(applicant=applicant)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
if serializer.instance.applicant != self.request.user:
|
||||
raise PermissionDenied("You can only modify your own applications.")
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
if instance.applicant != self.request.user:
|
||||
raise PermissionDenied("You can only delete your own applications.")
|
||||
instance.delete()
|
||||
Reference in new issue
Block a user