✨ feat(jobs): add models, serializers, views, and routes for Company, JobPosting, and JobApplication

This commit is contained in:
sankalp committed 2025-07-12 21:47:53 +05:45
1 parent a8b1c4d9dd
commit c19cea1b84
5 files changed
+425 -3

No files matched your search

+115 -1
View File
@@ -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")
+117
View File
@@ -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"]
+119
View File
@@ -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"]
+9
View File
@@ -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
View File
@@ -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()