Specification Sheet for Web Application Development with Python
Level-4
Job 01: Develop a job Portal
Develop a job portal using Django. In this project you have to incorporate multiple
recruiters and multiple job seekers from different domains on one platform. A single
recruiter can post multiple job openings using his/her account. A single job seeker can
apply for multiple openings based on his skills. Both recruiters and job seekers are able
to manage their account using a profile manager.
Job Specification Information:
1. Create a project named Name_ID_JobPortal.
2. Develop a registration page using the following fields
(Username, Display name, Email, Password, Confirm Password, User type)
3. Develop a login page using the following fields
(Username and Password)
4. Develop a profile creation page based on user type
a. Recruiters
(Company information)
b. Jobseekers (Skills set and resume upload option)
5. Develop a job posting page for recruiters
(Title, Number of openings, Category,
Job description, Skills set)
6. Develop a job applying page for jobseeker (Search)
7. Develop a skill matching page for both recruiters and jobseeker (Dashboard for
skill matched job)
Job Specification Information:
1. Create a Django Project. (Naming Convention: Name_ID_Project)
2. Create a Database.
3. Store the Database.
Models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
USER_TYPE_CHOICES = [
("recruiter", "Recruiter"),
("jobseeker", "Jobseeker"),
]
user = models.OneToOneField(User, on_delete=models.CASCADE)
display_name = models.CharField(max_length=100)
user_type = models.CharField(max_length=10, choices=USER_TYPE_CHOICES)
# For recruiters
company_name = models.CharField(max_length=100, blank=True, null=True)
company_description = models.TextField(blank=True, null=True)
# For jobseekers
skills = models.TextField(blank=True, null=True) # Comma-separated skills
resume = models.FileField(upload_to="resumes/", blank=True, null=True)
def __str__(self):
return f"{self.user.username} - {self.user_type}"
class Job(models.Model):
CATEGORY_CHOICES = [
("tech", "Technology"),
("finance", "Finance"),
("healthcare", "Healthcare"),
("education", "Education"),
("other", "Other"),
]
title = models.CharField(max_length=200)
recruiter = models.ForeignKey(User, on_delete=models.CASCADE, related_name="jobs")
number_of_openings = models.PositiveIntegerField()
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)
description = models.TextField()
skills_required = models.TextField() # Comma-separated skills
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
class Application(models.Model):
STATUS_CHOICES = [
("pending", "Pending"),
("shortlisted", "Shortlisted"),
("selected", "Selected"),
("rejected", "Rejected"),
]
jobseeker = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="applications"
)
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name="applications")
applied_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default="pending"
)
# Store jobseeker details at time of application
skills = models.TextField(blank=True, null=True) # Jobseeker's skills
resume = models.FileField(upload_to="application_resumes/", blank=True, null=True) # Jobseeker's resume
cover_letter = models.TextField(blank=True, null=True) # Optional cover letter
class Meta:
unique_together = ("jobseeker", "job")
def __str__(self):
return f"{self.jobseeker.username} applied for {self.job.title}"
forms.py
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from .models import UserProfile, Job, Application
class UserRegisterForm(UserCreationForm):
email = forms.EmailField()
display_name = forms.CharField(max_length=100)
user_type = forms.ChoiceField(choices=[('recruiter', 'Recruiter'), ('jobseeker', 'Jobseeker')])
class Meta:
model = User
fields = ['username', 'email', 'password1', 'password2']
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['display_name', 'user_type', 'company_name', 'company_description', 'skills', 'resume']
widgets = {
'company_description': forms.Textarea(attrs={'rows': 3}),
'skills': forms.Textarea(attrs={'rows': 3}),
}
class JobForm(forms.ModelForm):
class Meta:
model = Job
fields = ['title', 'number_of_openings', 'category', 'description', 'skills_required']
widgets = {
'description': forms.Textarea(attrs={'rows': 4}),
'skills_required': forms.Textarea(attrs={'rows': 3}),
}
class ApplicationForm(forms.ModelForm):
class Meta:
model = Application
fields = ['skills', 'resume', 'cover_letter']
widgets = {
'skills': forms.Textarea(attrs={'rows': 3, 'placeholder': 'Enter your skills (comma-separated)'}),
'cover_letter': forms.Textarea(attrs={'rows': 5, 'placeholder': 'Write your cover letter here...'}),
}
views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.db.models import Q, Count
from .forms import UserRegisterForm, UserProfileForm, JobForm, ApplicationForm
from .models import UserProfile, Job, Application
def register(request):
if request.method == 'POST':
form = UserRegisterForm(request.POST)
if form.is_valid():
user = form.save()
UserProfile.objects.create(
user=user,
display_name=form.cleaned_data['display_name'],
user_type=form.cleaned_data['user_type']
)
login(request, user)
return redirect('portal:profile')
# else:
# messages.error(request, "Registration failed. Please correct the errors below.")
# print(form.errors)
else:
form = UserRegisterForm()
return render(request, 'portal/register.html', {'form': form})
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user:
login(request, user)
return redirect("portal:dashboard")
else:
messages.error(request, "Invalid credentials")
return redirect("portal:login")
return render(request, 'portal/login.html')
@login_required
def profile(request):
profile, created = UserProfile.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = UserProfileForm(request.POST, request.FILES, instance=profile)
if form.is_valid():
form.save()
return redirect('portal:dashboard')
else:
form = UserProfileForm(instance=profile)
return render(request, 'portal/profile.html', {'form': form})
@login_required
def dashboard(request):
profile, created = UserProfile.objects.get_or_create(user=request.user)
if profile.user_type == 'recruiter':
jobs = Job.objects.filter(recruiter=request.user).annotate(applied_count=Count("applications"))
return render(request, 'portal/recruiter_dashboard.html', {'jobs': jobs})
else:
applications = Application.objects.filter(jobseeker=request.user)
# Skill matching: jobs where skills match user's skills
user_skills = profile.skills.lower().split(',') if profile.skills else []
query = Q()
for skill in user_skills:
query |= Q(skills_required__icontains=skill.strip())
matched_jobs = Job.objects.filter(query).exclude(
id__in=applications.values_list('job_id', flat=True)
)[:10] # Limit to 10, exclude already applied jobs
return render(request, 'portal/jobseeker_dashboard.html', {
'applications': applications,
'matched_jobs': matched_jobs
})
@login_required
def post_job(request):
if request.method == 'POST':
form = JobForm(request.POST)
if form.is_valid():
job = form.save(commit=False)
job.recruiter = request.user
job.save()
return redirect('portal:dashboard')
else:
form = JobForm()
return render(request, 'portal/post_job.html', {'form': form, 'title': 'Post', 'submit': 'Post'})
def job_list(request):
jobs = Job.objects.all()
query = request.GET.get('query', '')
category = request.GET.get('category', '')
if query:
jobs = jobs.filter(
Q(title__icontains=query) |
Q(description__icontains=query) |
Q(skills_required__icontains=query)
)
if category and category != "all":
jobs = jobs.filter(category=category)
applied_jobs = []
if hasattr(request.user, 'userprofile') and request.user.userprofile.user_type == 'jobseeker':
applied_jobs = Application.objects.filter(jobseeker=request.user).values_list('job_id', flat=True)
return render(request, 'portal/job_list.html', {
'jobs': jobs,
'applied_jobs': applied_jobs,
'query': query,
'category': category
})
@login_required
def job_details(request, job_id):
job = get_object_or_404(Job, id=job_id)
applied = False
if request.user.is_authenticated and hasattr(request.user, 'userprofile') and request.user.userprofile.user_type == 'jobseeker':
applied = Application.objects.filter(jobseeker=request.user, job=job).exists()
return render(request, 'portal/job_details.html', {'job': job, 'applied': applied})
@login_required
def apply_job(request, job_id):
job = get_object_or_404(Job, id=job_id)
profile = UserProfile.objects.get(user=request.user)
# Check if already applied
if Application.objects.filter(jobseeker=request.user, job=job).exists():
messages.info(request, 'You have already applied for this job.')
return redirect('portal:job_list')
if request.method == 'POST':
form = ApplicationForm(request.POST, request.FILES)
if form.is_valid():
application = form.save(commit=False)
application.jobseeker = request.user
application.job = job
application.save()
messages.success(request, 'Application submitted successfully!')
return redirect('portal:job_list')
else:
# Pre-fill form with user's profile data
initial_data = {
'skills': profile.skills,
'resume': profile.resume,
}
form = ApplicationForm(initial=initial_data)
return render(request, 'portal/apply_job.html', {
'form': form,
'job': job
})
@login_required
def manage_applications(request, job_id):
job = get_object_or_404(Job, id=job_id, recruiter=request.user)
applications = Application.objects.filter(job=job)
if request.method == 'POST':
app_id = request.POST.get('application_id')
new_status = request.POST.get('status')
application = get_object_or_404(Application, id=app_id, job=job)
application.status = new_status
application.save()
messages.success(request, f'Application status updated to {new_status}.')
return redirect('portal:manage_applications', job_id=job_id)
return render(request, 'portal/manage_applications.html', {
'job': job,
'applications': applications
})
@login_required
def edit_job(request, job_id):
job = get_object_or_404(Job, id=job_id, recruiter=request.user)
if request.method == 'POST':
form = JobForm(request.POST, instance=job)
if form.is_valid():
form.save()
messages.success(request, 'Job updated successfully!')
return redirect('portal:dashboard')
else:
form = JobForm(instance=job)
return render(request, 'portal/post_job.html', {'form': form, 'job': job, 'title': 'Edit', 'submit': 'Update'})
@login_required
def delete_job(request, job_id):
job = get_object_or_404(Job, id=job_id, recruiter=request.user)
if request.method == 'POST':
job.delete()
messages.success(request, 'Job deleted successfully!')
return redirect('portal:dashboard')
return render(request, 'portal/delete_job.html', {'job': job})
@login_required
def logout_view(request):
logout(request)
return redirect('portal:login')
urls.py
from django.urls import path
from . import views
app_name = 'portal'
urlpatterns = [
path('login/', views.login_view, name='login'),
path('register/', views.register, name='register'),
path('logout/', views.logout_view, name='logout'),
path('profile/', views.profile, name='profile'),
path('dashboard/', views.dashboard, name='dashboard'),
path('post-job/', views.post_job, name='post_job'),
path('', views.job_list, name='job_list'),
path('apply/<int:job_id>/', views.apply_job, name='apply_job'),
path('job/<int:job_id>/', views.job_details, name='job_details'),
path('manage-applications/<int:job_id>/', views.manage_applications, name='manage_applications'),
path('edit-job/<int:job_id>/', views.edit_job, name='edit_job'),
path('delete-job/<int:job_id>/', views.delete_job, name='delete_job'),
]
project/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('portal.urls', namespace='portals')),
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Job Portal{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
.regilog {
text-decoration: none;
color: #0d6efd;
}
.regilog:hover {
color: #08bf8eff;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="{% url 'portal:job_list' %}">Job Portal</a>
<div class="navbar-nav ms-auto">
{% if user.is_authenticated %}
<a class="nav-link" href="{% url 'portal:job_list' %}">Jobs</a>
<a class="nav-link" href="{% url 'portal:dashboard' %}">Dashboard</a>
<a class="nav-link" href="{% url 'portal:profile' %}">Profile</a>
<a class="nav-link" href="{% url 'portal:logout' %}">Logout</a>
{% else %}
<a class="nav-link" href="{% url 'portal:login' %}">Login</a>
<a class="nav-link" href="{% url 'portal:register' %}">Register</a>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
register.html
{% extends 'portal/base.html' %}
{% block title %}Register{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Create Account</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="display_name" class="form-label">Display Name</label>
<input type="text" class="form-control" id="display_name" name="display_name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" class="form-control" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="user_type" class="form-label">User Type</label>
<select class="form-control" id="user_type" name="user_type" required>
<option value="">Select User Type</option>
<option value="recruiter">Recruiter</option>
<option value="jobseeker">Jobseeker</option>
</select>
</div>
<div class="mb-3">
<label for="password1" class="form-label">Password</label>
<input type="password" class="form-control" id="password1" name="password1" required>
</div>
<div class="mb-3">
<label for="password2" class="form-label">Confirm Password</label>
<input type="password" class="form-control" id="password2" name="password2" required>
</div>
<button type="submit" class="btn btn-primary w-100">Register</button>
</form>
<div class="text-center mt-3">
<p>Already have an account? <a class="regilog" href="{% url 'portal:login' %}"><b>Login here</b></a></p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
login.html
{% extends 'portal/base.html' %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Login</h3>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary w-100">Login</button>
</form>
<div class="text-center mt-3">
<p>Don't have an account? <a class="regilog" href="{% url 'portal:register' %}"><b>Register here</b></a></p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
profile.html
{% extends 'portal/base.html' %}
{% block title %}Profile{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Complete Your Profile</h3>
</div>
<div class="card-body">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="display_name" class="form-label">Display Name</label>
<input type="text" class="form-control" id="display_name" name="display_name" value="{{ form.display_name.value|default:'' }}" required>
</div>
<div class="mb-3">
<label for="user_type" class="form-label">User Type</label>
<select class="form-control" id="user_type" name="user_type" required>
<option value="">Select User Type</option>
<option value="recruiter" {% if form.user_type.value == 'recruiter' %}selected{% endif %}>Recruiter</option>
<option value="jobseeker" {% if form.user_type.value == 'jobseeker' %}selected{% endif %}>Jobseeker</option>
</select>
</div>
{% if form.user_type.value == 'recruiter' %}
<div class="mb-3">
<label for="company_name" class="form-label">Company Name</label>
<input type="text" class="form-control" id="company_name" name="company_name" value="{{ form.company_name.value|default:'' }}">
</div>
<div class="mb-3">
<label for="company_description" class="form-label">Company Description</label>
<textarea class="form-control" id="company_description" name="company_description" rows="4">{{ form.company_description.value|default:'' }}</textarea>
</div>
{% else %}
<div class="mb-3">
<label for="skills" class="form-label">Skills (comma-separated)</label>
<input type="text" class="form-control" id="skills" name="skills" value="{{ form.skills.value|default:'' }}">
</div>
<div class="mb-3">
<label for="resume" class="form-label">Resume</label>
<input type="file" class="form-control" id="resume" name="resume">
</div>
{% endif %}
<button type="submit" class="btn btn-primary">Save Profile</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
recruiter-dashboard.html
{% extends 'portal/base.html' %}
{% block title %}Recruiter Dashboard{% endblock %}
{% block content %}
<div class="container mt-5">
<h2>Welcome, {{ user.username }}!</h2>
<p>You are logged in as a Recruiter.</p>
<div class="mb-4">
<a href="{% url 'portal:post_job' %}" class="btn btn-primary">Post a New Job</a>
</div>
<h3>Your Jobs:</h3>
{% if jobs %}
<div class="row">
{% for job in jobs %}
<div class="col-md-4 mb-4">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title"><a href="{% url 'portal:job_details' job.id %}" class="text-decoration-none">{{ job.title }}</a></h5>
<p class="card-text"><strong>Skills:</strong> {{ job.skills_required }}</p>
<p class="card-text"><strong>Applied: {{ job.applied_count }} </strong></p>
<small class="text-muted">Posted on: {{ job.created_at|date:"M d, Y" }}</small>
<div class="mt-3">
<a href="{% url 'portal:manage_applications' job.id %}" class="btn btn-sm btn-info">View Applications</a>
<a href="{% url 'portal:edit_job' job.id %}" class="btn btn-sm btn-warning">Edit Job</a>
<a href="{% url 'portal:delete_job' job.id %}" class="btn btn-sm btn-danger">Delete Job</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>You haven't posted any jobs yet.</p>
{% endif %}
</div>
{% endblock %}
job-seeker-dashboard.html
{% extends 'portal/base.html' %}
{% block title %}Jobseeker Dashboard{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row">
<div class="col-12">
<h2 class="mb-4">Welcome, {{ user.username }}!</h2>
<p>You are logged in as a Jobseeker.</p>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="card shadow mb-4">
<div class="card-header bg-success text-white">
<h5 class="card-title mb-0">Your Applications</h5>
</div>
<div class="card-body">
{% if applications %}
<div class="list-group list-group-flush">
{% for application in applications %}
<div class="list-group-item">
<h6 class="mb-1"><a href="{% url 'portal:job_details' application.job.id %}">{{ application.job.title }}</a></h6>
<p class="mb-1">{{ application.job.recruiter.username }}</p>
<small class="text-muted">Applied on {{ application.applied_at|date:"M d, Y" }}</small>
<span class="badge bg-{% if application.status == 'pending' %}warning{% elif application.status == 'accepted' %}success{% else %}danger{% endif %} ms-2">{{ application.status|title }}</span>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted">You haven't applied to any jobs yet.</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow mb-4">
<div class="card-header bg-info text-white">
<h5 class="card-title mb-0">Matching Jobs with Skills</h5>
</div>
<div class="card-body">
{% if matched_jobs %}
<div class="list-group list-group-flush">
{% for job in matched_jobs %}
<div class="list-group-item">
<h6 class="mb-1"><a href="{% url 'portal:job_details' job.id %}">{{ job.title }}</a></h6>
<p class="mb-1">{{ job.recruiter.username }}</p>
<small class="text-muted">{{ job.category|title }}</small>
<a href="{% url 'portal:apply_job' job.id %}" class="btn btn-sm btn-primary mt-2">Apply Now</a>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-muted">No recommended jobs available. Update your skills in your profile.</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h5 class="card-title mb-0">All Available Jobs</h5>
</div>
<div class="card-body">
<a href="{% url 'portal:job_list' %}" class="btn btn-primary">Browse All Jobs</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
post-job.html
{% extends 'portal/base.html' %}
{% block title %}Post Job{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-primary text-white text-center">
<h3 class="card-title mb-0">{{title}} a New Job</h3>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="title" class="form-label">Job Title</label>
<input type="text" class="form-control" id="title" name="title" value="{{ form.title.value|default:'' }}" required>
</div>
<div class="mb-3">
<label for="number_of_openings" class="form-label">Number of Openings</label>
<input type="number" class="form-control" id="number_of_openings" name="number_of_openings" value="{{ form.number_of_openings.value|default:'' }}" required>
</div>
<div class="mb-3">
<label for="category" class="form-label">Category</label>
<select class="form-control" id="category" name="category" required>
<option value="">Select Category</option>
<option value="tech" {% if form.category.value == 'tech' %}selected{% endif %}>Technology</option>
<option value="finance" {% if form.category.value == 'finance' %}selected{% endif %}>Finance</option>
<option value="healthcare" {% if form.category.value == 'healthcare' %}selected{% endif %}>Healthcare</option>
<option value="education" {% if form.category.value == 'education' %}selected{% endif %}>Education</option>
<option value="other" {% if form.category.value == 'other' %}selected{% endif %}>Other</option>
</select>
</div>
<div class="mb-3">
<label for="description" class="form-label">Job Description</label>
<textarea class="form-control" id="description" name="description" rows="4" required>{{ form.description.value|default:'' }}</textarea>
</div>
<div class="mb-3">
<label for="skills_required" class="form-label">Skills Required (comma-separated)</label>
<input type="text" class="form-control" id="skills_required" name="skills_required" value="{{ form.skills_required.value|default:'' }}" required>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary">{{submit}} Job</button>
<a href="{% url 'portal:dashboard' %}" class="btn btn-secondary">Cancel</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
job-lists.html
{% extends 'portal/base.html' %}
{% block title %}Available Jobs{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row">
<div class="col-12">
<h2 class="mb-4">Available Jobs</h2>
<form method="get" class="mb-4">
<div class="row g-3">
<div class="col-md-6">
<input type="text" name="query" class="form-control" placeholder="Search jobs..." value="{{ query }}">
</div>
<div class="col-md-4">
<select name="category" class="form-select">
<option value="all" {% if not category or category == 'all' %}selected{% endif %}>All Categories</option>
<option value="tech" {% if category == 'tech' %}selected{% endif %}>Technology</option>
<option value="finance" {% if category == 'finance' %}selected{% endif %}>Finance</option>
<option value="healthcare" {% if category == 'healthcare' %}selected{% endif %}>Healthcare</option>
<option value="education" {% if category == 'education' %}selected{% endif %}>Education</option>
<option value="other" {% if category == 'other' %}selected{% endif %}>Other</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">Search</button>
</div>
</div>
</form>
</div>
</div>
<div class="row">
{% for job in jobs %}
<div class="col-md-4 mb-4">
<div class="card shadow h-100">
<div class="card-header bg-light">
<h5 class="card-title mb-0"><a href="{% url 'portal:job_details' job.id %}" class="text-decoration-none">{{ job.title }}</a></h5>
<small class="text-muted">{{ job.recruiter.username }}</small>
</div>
<div class="card-body">
<div class="mb-2">
<span class="badge bg-secondary">{{ job.category|title }}</span>
<span class="badge bg-info">{{ job.number_of_openings }} openings</span>
</div>
<div class="mb-3">
<strong>Skills Required:</strong>
<p class="mb-0">{{ job.skills_required }}</p>
</div>
<small class="text-muted">Posted on {{ job.created_at|date:"M d, Y" }}</small>
</div>
<div class="card-footer">
{% if user.is_authenticated and user.userprofile.user_type == 'jobseeker' %}
{% if job.id not in applied_jobs %}
<a href="{% url 'portal:apply_job' job.id %}" class="btn btn-primary">Apply Now</a>
{% else %}
<button class="btn btn-success" disabled>Already Applied</button>
{% endif %}
{% endif %}
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info">
<h4>No jobs available</h4>
<p>Check back later for new job opportunities.</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
job-detail.html
{% extends 'portal/base.html' %}
{% block title %}{{ job.title }} - Job Details{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="mb-3">
<a href="{% url 'portal:job_list' %}" class="btn btn-secondary">← Back to Jobs</a>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">{{ job.title }}</h3>
<small class="text-muted">Posted by: {{ job.recruiter.username }}</small>
</div>
<div class="card-body">
<div class="mb-4">
<h5>Job Details:</h5>
<p><strong>Category:</strong> {{ job.get_category_display }}</p>
<p><strong>Openings:</strong> {{ job.number_of_openings }}</p>
<p><strong>Description:</strong> {{ job.description }}</p>
<p><strong>Required Skills:</strong> {{ job.skills_required }}</p>
<p><strong>Posted on:</strong> {{ job.created_at|date:"d M Y" }}</p>
</div>
{% if user.is_authenticated and user.userprofile.user_type == 'jobseeker' %}
<div class="text-center">
{% if applied %}
<button class="btn btn-success btn-lg" disabled>You have already applied for this job</button>
{% else %}
<a href="{% url 'portal:apply_job' job.id %}" class="btn btn-primary btn-lg">Apply Now</a>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
apply-job.html
{% extends 'portal/base.html' %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Apply for: {{ job.title }}</h3>
<small class="text-muted">Posted by: {{ job.recruiter.username }}</small>
</div>
<div class="card-body">
<div class="mb-4">
<h5>Job Details:</h5>
<p><strong>Category:</strong> {{ job.get_category_display }}</p>
<p><strong>Openings:</strong> {{ job.number_of_openings }}</p>
<p><strong>Description:</strong> {{ job.description }}</p>
<p><strong>Required Skills:</strong> {{ job.skills_required }}</p>
</div>
<h5>Your Application:</h5>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="mb-3">
<label for="skills" class="form-label">Your Skills</label>
<textarea class="form-control" id="skills" name="skills" rows="3" placeholder="Enter your skills (comma-separated)" required>{{ form.skills.value|default:'' }}</textarea>
{% if form.skills.errors %}
<div class="text-danger">{{ form.skills.errors }}</div>
{% endif %}
</div>
<div class="mb-3">
<label for="resume" class="form-label">Resume</label>
<input type="file" class="form-control" id="resume" name="resume" accept=".pdf,.doc,.docx">
{% if form.resume.errors %}
<div class="text-danger">{{ form.resume.errors }}</div>
{% endif %}
<div class="form-text">Upload your resume (PDF, DOC, DOCX)</div>
</div>
<div class="mb-3">
<label for="cover_letter" class="form-label">Cover Letter</label>
<textarea class="form-control" id="cover_letter" name="cover_letter" rows="5" placeholder="Write your cover letter here...">{{ form.cover_letter.value|default:'' }}</textarea>
{% if form.cover_letter.errors %}
<div class="text-danger">{{ form.cover_letter.errors }}</div>
{% endif %}
</div>
<button type="submit" class="btn btn-primary w-100">Submit Application</button>
</form>
<div class="text-center mt-3">
<a href="{% url 'portal:job_list' %}" class="btn btn-secondary">Cancel</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
manage-application.html
{% extends 'portal/base.html' %}
{% block title %}Manage Applications for {{ job.title }}{% endblock %}
{% block content %}
<div class="container mt-5">
<h2>Applications for: {{ job.title }}</h2>
<p>Posted by: {{ job.recruiter.username }}</p>
{% if applications %}
<div class="row">
{% for application in applications %}
<div class="col-md-6 mb-4">
<div class="card shadow">
<div class="card-body">
<h5 class="card-title">{{ application.jobseeker.username }}</h5>
<p class="card-text"><strong>Skills:</strong> {{ application.skills }}</p>
<p class="card-text"><strong>Cover Letter:</strong> {{ application.cover_letter|truncatechars:100 }}</p>
<p class="card-text"><strong>Status:</strong> <span class="badge bg-{% if application.status == 'pending' %}warning{% elif application.status == 'shortlisted' %}info{% elif application.status == 'selected' %}success{% else %}danger{% endif %}">{{ application.status|title }}</span></p>
{% comment %} <p class="card-text"><strong>Applied:</strong> {{ applications|length }}</p> {% endcomment %}
<p class="card-text"><small class="text-muted">Applied on: {{ application.applied_at|date:"M d, Y" }}</small></p>
{% if application.resume %}
<a href="{{ application.resume.url }}" class="btn btn-sm btn-outline-primary" target="_blank">View Resume</a>
{% endif %}
<form method="post" class="d-inline">
{% csrf_token %}
<input type="hidden" name="application_id" value="{{ application.id }}">
<select name="status" class="form-select form-select-sm d-inline w-auto">
<option value="pending" {% if application.status == 'pending' %}selected{% endif %}>Pending</option>
<option value="shortlisted" {% if application.status == 'shortlisted' %}selected{% endif %}>Shortlisted</option>
<option value="selected" {% if application.status == 'selected' %}selected{% endif %}>Selected</option>
<option value="rejected" {% if application.status == 'rejected' %}selected{% endif %}>Rejected</option>
</select>
<button type="submit" class="btn btn-sm btn-primary">Update</button>
</form>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>No applications yet.</p>
{% endif %}
<div class="mt-4">
<a href="{% url 'portal:dashboard' %}" class="btn btn-secondary">Back to Dashboard</a>
</div>
</div>
{% endblock %}
delete-job.html
{% extends 'portal/base.html' %}
{% block title %}Delete Job{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header bg-danger text-white">
<h3 class="card-title mb-0">Delete Job</h3>
</div>
<div class="card-body">
<p>Are you sure you want to delete the job "<strong>{{ job.title }}</strong>"?</p>
<p class="text-muted">This action cannot be undone.</p>
<form method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Yes, Delete Job</button>
<a href="{% url 'portal:dashboard' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
settings.py
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-jvf&_zpgdrh&r9uah0xo(97yy&fez$u+c1#m7uxvfj17s#!-uq'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'portal',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'JobPortal.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'JobPortal.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
LOGIN_URL = "portal:login"
No comments:
Post a Comment