A powerful and user-friendly Job Portal application API designed to connect candidates and employers. This platform provides essential features for job management, application tracking, and subscription services, making the hiring process seamless and efficient.
The Django Job Portal is a robust application API designed to streamline the recruitment process. It provides separate, tailored interfaces for candidates and employers, enabling job searching, posting, and application management. With subscription-based enhancements, this platform scales to meet diverse hiring needs.
The project is modular and organized for scalability:
- Accounts: Handles user authentication and profile management.
- Candidates: Features for job seekers, including profile creation and application tracking.
- Employers: Tools for employers to manage job postings and applications.
- Jobs: Comprehensive job management functionalities.
- Applications: Tracks job applications and candidate statuses.
- Subscriptions: Manages plans and benefits for premium features.
Authentication is the process of verifying the identity of a user or system. It ensures that someone trying to access a system or perform an action is indeed who they claim to be. This Job Portal uses custom JWT authentication to verify identities.
JWTs (JSON Web Tokens) are small, secure ways to share information between two parties. They act like digital ID cards for users, holding encoded details about who the user is and what they are allowed to do. Because JWTs contain all necessary information within them, they are well-suited for stateless authentication, meaning web apps don’t need to store extra data about users on the server.
A JSON Web Token (JWT) comprises three main parts: a header, a payload, and a signature. These segments are Base64Url-encoded and concatenated with periods (.) to form the complete JWT.
-
Header: The header is a JSON object that usually contains two fields: a token type (commonly JWT) and a signing algorithm (e.g., RS256 for RSA SHA-256). It provides details about how the JWT is encoded and secured.
-
Payload: The payload is a JSON object containing claims and information about the user and other relevant data. Claims are key-value pairs and can be registered, public, or private.
Common keys within JWT claims include:
iss
(Issuer) — identifies the entity that issued the JWT.sub
(Subject) — identifies the user the token is for by storing the user’s unique identifier.aud
(Audience) — specifies the recipients allowed to process the token.exp
(Expiration Time) — defines when the token expires, in seconds since the Unix epoch.nbf
(Not Before) — defines the earliest time the token can be considered valid, in seconds since the Unix epoch.iat
(Issued At) — indicates when the token was issued in seconds since the Unix epoch.
-
Signature: The signature is a cryptographic hash created using the encoded header, payload, and a secret key. It ensures the token’s integrity and authenticity. The encoded header and payload are concatenated with a period (.) and then hashed with the secret key, using a specified algorithm to generate the signature.
Example of signature creation with RSA SHA-256:
RSASHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), private_key )
What is Custom JWT Authentication?
Custom JWT authentication means creating a personalized way of using JWTs (JSON Web Tokens) to fit the specific needs of a project. Instead of using ready-made tools (like django-rest-framework-simplejwt
), we build our own methods for:
- Creating tokens: Choosing what information to include, setting custom expiration times, or using specific security algorithms (e.g., RSA for stronger security).
- Checking tokens: Writing our own methods to verify tokens to match special security needs or add extra checks.
- Connecting with user data: Adding specific user details or roles that fit the business logic of the project.
Extending BaseAuthentication
and overriding the .authenticate(self, request)
method. The method should return a two-tuple of (user, auth)
if authentication succeeds, or None
otherwise. In some circumstances, instead of returning None
, we may want to raise an AuthenticationFailed
exception from the .authenticate()
method.
- Flexibility: Define token payload and structure according to our application's needs.
- Enhanced Security: Use custom algorithms, rotate signing keys, or implement more advanced claim verification.
- Custom Claims: Include project-specific claims like user roles, permissions, or other contextual data.
- Integration with Microservices: Implement JWTs with custom claims to manage multi-service authentication seamlessly.
Authorization in the context of software and web applications refers to the rights or access levels given to users that determine what actions they can perform within the system. Permissions are used to enforce security and control over who can read, write, update, or delete data.
- Custom User-Based Permissions: Permissions are handled based on the user type (candidate or employer), with role-based access to different resources. Used extending
BasePermission
class and overridehas_permission
method. - Custom User Model: The user model extends Django’s
AbstractUser
, managed by a customBaseUserManager
class to handle user registration, login, and profile management. - Email and Phone Login: Allows users to log in using either email or phone number via a custom authentication backend.
- Profile creation and management.
- Search and apply for jobs.
- Track application statuses.
- Post, edit, and manage job listings.
- Review candidate applications.
- Subscription-based access to premium tools.
- Advanced job filtering and searching.
- Secure job posting with detailed descriptions.
- Centralized application tracking.
- Notifications for application updates.
- Multiple subscription tiers with premium benefits.
- Easy upgrades and secure payments.
Firstly, a custom Users model is created by extending Django's built-in AbstractUser
class. A custom model manager, UserManager
, is also created to handle user creation and superuser creation.
- Custom Fields: We can add extra fields (e.g., phone number, address) to our user model.
- Custom Logic: We can implement custom behavior for user management (e.g., overriding user methods or validation rules).
- Future Flexibility: We can future-proof our project by having a more flexible user model that can be extended or modified easily.
- Avoiding Migration Issues: Starting with
AbstractUser
or a custom user model helps us avoid complex migration issues later if we decide to customize the default User model.
- Custom Query Methods: We can define custom query methods to encapsulate complex or reusable logic, making our code cleaner and more maintainable.
- Enhanced Code Reusability: We can centralize database queries within the manager, making it easy to reuse these methods across different parts of the project.
- Separation of Concerns: By moving query logic into a model manager, we keep the model itself focused on representing data, and improving code readability and organization.
- Creating Custom Object Creation Methods: We can override, extend default methods like
create_user()
orcreate_superuser()
to customize how model instances are created. - Custom QuerySets: We can attach custom
QuerySet
methods to provide a fluent and chainable interface for querying data.
The base URL for all API requests is: https://127.0.0.1:8000
is defined as {{base_url}}
.
- Authorization: Bearer Token
- Request and response type: JSON
POST {{base_url}}/account/register/
None (No authentication required for user registration)
Registers a new user by serializing the provided data and saving the user instance. After saving the user, a welcome email with an OTP is sent asynchronously using Celery.
Fetches the user data using the following PostgreSQL query:
INSERT INTO user (id, email, contract_number, user_type, password, otp) VALUES (id_value, email_value, contract_number_value, user_type_value, password_value, otp_value);
The UserSerializer
is used to validate and serialize the incoming registration data. If the data is valid, the user is saved, and the OTP is generated. After successful registration, the OTP is sent via email asynchronously using Celery.
- Custom Field Validation: Methods like
validate_email
andvalidate_contract_number
are used for custom validation logic specific to each field. - Uniqueness Check: Both the email and contract number are checked against the Users model to ensure they are unique before saving, preventing duplicates.
- Password Validation: The password field is marked as
write_only=True
(not returned in responses) and uses Django's built-invalidate_password
function to ensure the password meets Django's default password validation rules (such as length, complexity, etc.). - Format and Length Check: For
contract_number
, additional checks are performed to ensure it has the correct length (11 digits) and proper format (digits only, with country code handling). - Error Handling: If any validation rule fails, a
serializer.ValidationError
is raised with an appropriate error message, which will be returned in the response to the client.
Celery is an asynchronous task queue system used for managing background tasks. It allows applications to offload time-consuming operations to worker processes, improving performance and responsiveness.
- Asynchronous Processing: Sending emails can take time, and handling it asynchronously prevents delays in the user registration API response.
- Scalability: Celery ensures that tasks like sending OTPs can scale efficiently, even under heavy traffic.
- Reliability: By leveraging Celery with a message broker like Redis, tasks can be queued and retried if failures occur.
The body of the POST request should include the necessary data for registering a user, typically containing fields such as id
, email
, contract_number
, user_type
, and password
, as required by the custom user model.
{
"email": "user1@gmail.com",
"contract_number": "017111111111",
"user_type": "candidate",
"password": "1234"
}
Returns a success message with the generated OTP.
{
"status": "OTP has been generated",
"otp": "755745"
}
Returns errors if the provided registration data is invalid.
{
"contract_number": [
"Contract number must contain 11 digits (e.g., 01XXXXXXXXX)."
],
"password": [
"This password is too short. It must contain at least 8 characters.",
"This password is too common.",
"This password is entirely numeric."
]
}
POST {{base_url}}/account/verify_otp/
Verifies the OTP and email combination for the user during registration. If valid, the user is marked as verified, and the registration process is completed.
The body of the POST request should include the otp
and email
fields.
The query fetches the user by matching the provided OTP and email from the database:
SELECT * FROM user WHERE email = request.data['email'] AND otp = request.data['otp'];
The request data is validated using the OtpVerificationSerializer
. The serializer ensures the OTP is a 6-digit numeric value and that both the OTP and email exist in the database.
When a user completes the registration process, an OTP is generated automatically. The OTP is created inside the save
method of the Users
model using the generate_otp()
function, which generates a random OTP of a specified length.
The generate_otp()
function uses Python's random.choice()
to generate a string of digits of length defined by OTP_LENGTH
. This OTP is saved to the user model’s otp
field.
After the OTP is generated and saved to the model, a Celery task (send_welcome_email
) is triggered to send the OTP to the user’s email. This is done asynchronously using Celery, with Redis as the message broker. The task sends an email containing the OTP to the user.
Celery is configured with Redis as the broker and result backend. The email backend is set up to use Gmail's SMTP server for sending OTP emails.
When the user submits the OTP (via the ValidatedOtpView
API endpoint), the OtpVerificationSerializer
is used to validate the OTP and email provided. If the OTP and email match the records in the database, the user’s is_verified
field is set to True
, and the OTP is marked as verified. A success message is then returned to confirm registration.
If the OTP or email doesn’t match, or any error occurs during validation, appropriate error responses are returned. Additionally, if an exception occurs in the OTP validation process, an internal server error message is sent.
A custom signal otp_verified
is defined using Signal()
to handle actions after OTP verification.
The signal is triggered with otp_verified.send(sender=user.__class__, instance=user)
after successfully verifying the OTP in the ValidatedOtpView
.
The create_user_profile
function, decorated with @receiver(otp_verified)
, listens for the signal and is triggered when the signal is sent.
Inside the receiver function, based on the user_type
('candidate' or 'employer'), either a CandidateProfile
or EmployerProfile
is created and linked to the user.
The signal ensures automatic profile creation for the user after successful OTP verification.
{
"otp": "765146",
"email": "user1@gmail.com"
}
Returns the success message if the OTP and email are valid and the user is successfully verified.
{
"status": "Registration successful"
}
Returns validation errors if the OTP or email combination is incorrect.
{
"otp": [
"OTP does not exist."
],
"email": [
"Email does not exist."
]
}
Returns a generic error message if an unexpected error occurs during the OTP validation process.
POST {{base_url}}/#/
This view is used for logging in users with either their email or phone number and password. Upon successful login, a token is generated for the user.
The query fetches the user based on the provided email_or_phone
value, matching it with either the email
or contract_number
field in the database:
SELECT * FROM user WHERE email = request.data['email_or_phone'] OR contract_number = request.data['email_or_phone'];
The request data is validated using the LoginSerializer
. This serializer first checks whether the email_or_phone
value is a valid email or phone number. It then ensures the provided credentials (password) match the authenticated user and that the user has been verified.
A custom authentication backend called EmailOrPhoneBackend
extends ModelBackend
to allow users to log in using either an email or phone number.
The authenticate
method checks if the username contains an '@' symbol to differentiate between email and phone number.
Depending on the input type, the method queries the Users
model using either the email
field or the contract_number
field.
The method verifies the provided password using the user.check_password()
function.
If the user exists and the password is valid, the user instance is returned; otherwise, None
is returned.
Added EmailOrPhoneBackend
to AUTHENTICATION_BACKENDS
in Django settings to enable the custom authentication method.
A LoginSerializer
class is created with fields email_or_phone
and password
. The password
field is set to write_only=True
for security.
In the validate_identifier
method, an EmailValidator()
is used to check if the input is an email. If validation fails, the input is checked if it’s an 11-digit number to verify if it’s a valid phone number. If neither check passes, a ValidationError
is raised with a message prompting for a valid email or phone number.
Depending on the input type, flags are set in self.context
as is_email
or is_phone
to use later during the authentication process. This helps determine how the EmailOrPhoneBackend
should handle the input.
In the validate
method, email_or_phone
and password
are retrieved from the data dictionary.
auth_kwargs
is set up with username
and password
by default. If the input is identified as a phone number, auth_kwargs
is adjusted to use phone
instead of username
. This step aligns with how the EmailOrPhoneBackend
checks whether the input is an email or phone number for authentication.
The authenticate()
function is called using auth_kwargs
, which interacts with the EmailOrPhoneBackend
. This backend processes the input type accordingly and returns the user if authentication is successful. If authenticate()
returns None
, a ValidationError
is raised for invalid credentials.
If the user is found but not verified, a ValidationError
is raised prompting the user to complete OTP verification.
If validation is successful, the user object is added to the data dictionary and returned for further processing.
A token_generation
function is implemented to handle the creation of JWT tokens for authenticated users.
An expiration time (exp
) for the token is set to 30 minutes from the current time to ensure session security.
The token payload includes essential user information: id
, email
, user_type
, and contact_number
to support user identification and role-based logic.
The jwt.encode()
method is used with the payload, a SECRET_KEY
for encryption, and the HS256
algorithm to generate a secure token.
After successful user retrieval and validation in the LoginView
, the token_generation
function is called to create the token and include it in the response.
The generated token is returned as an access_token
, which the client can use for subsequent authenticated requests.
The body of the POST request should include email_or_phone
(either email or phone number) and password
.
{
"email_or_phone" : "user1@gmail.com",
"password" : "qwer0987"
}
Returns the generated access token if the user credentials are valid and the user is verified.
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTkzNCwiZW1haWwiOiJ1c2VyNzBAZ21haWwuY29tIiwidXNlcl90eXBlIjoiY2FuZGlkYXRlIiwiY29udGFjdF9udW1iZXIiOiIwMTcxMTExMTE3MCIsImV4cCI6MTczMTQ5NjI0OX0.jmR5bg3HXFMOIcMx3GhudjgkEo4dgKub18zxjj8atH4"
}
Returns validation errors if the input is invalid or the credentials do not match.
{
"error": "Token generation failed: Users matching query does not exist."
}
Returns a generic error message if an unexpected error occurs during the token generation process.
GET {{base_url}}/account/user_info/?page=1
page_no
: The page number to retrieve. Optional, default is 1.page_size
: The number of records per page. Optional, default is 2.
Open to all authenticated users.
Fetches a list of users, including their details, paginated.
Fetches all users from the user table, with pagination, using the following SQL query:
SELECT * FROM user LIMIT 10 OFFSET {page_offset};
The users are serialized using the UserDetailsProfileSerializer
, which includes a nested serializer for user_details
. The user_details
field fetches additional details based on the user's type (either candidate or employer), serialized using the CandidateDetailsProfileSerializer
or EmployerDetailsProfileSerializer
accordingly.
- Pagination Setup: In the
UserInfoView
, thePageNumberPagination
class handles pagination for the list of users. - Page Size: Defined as 10 using
paginator.page_size = 10
. - Pagination Query: Applied using
paginator.paginate_queryset(users, request)
. - Serializer: The paginated queryset is passed to the
UserDetailsProfileSerializer
withmany=True
. - Returning Paginated Response: Returned using
paginator.get_paginated_response(serializer.data)
. - Error Handling: Wrapped in a try-except block to handle potential errors.
- View Setup: Protected by custom authentication (
CustomAuthentication
). - Nested Serialization: Uses
SerializerMethodField
foruser_details
:- If the user is a "candidate", retrieves and serializes using
CandidateSerializer
. - If the user is an "employer", retrieves and serializes using
EmployerProfileSerializer
. - Returns
None
if the associated profile is not found.
- If the user is a "candidate", retrieves and serializes using
- Error Handling: Wrapped in a try-except block to catch unexpected errors.
Returns a paginated list of user data, including their profile details.
{
"count": 4,
"next": "http://127.0.0.1:8000/account/user_info/?page=2",
"previous": null,
"results": [
{
"id": 2,
"email": "user2@gmail.com",
"contract_number": "017111111112",
"user_type": "employer",
"user_details": {
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
}
},
{
"id": 3,
"email": "user1@gmail.com",
"contract_number": "01711111111",
"user_type": "candidate",
"user_details": {
"id": 1,
"profile_details": {
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Abc",
"address": "123 Main St, Springfield, USA",
"profile_pic": "/documents/profile_pictures/8.jpeg",
"bio": "A highly motivated software engineer with a passion for developing innovative programs.",
"social_links": [
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe"
],
"gender": "male",
"birth_date": "1995-06-15",
"user": 3
}
}
]
}
GET {{base_url}}/account/#-user-info/
Open to all authenticated users.
Fetches the authenticated user's profile based on their user_type
(either 'candidate' or 'employer').
Fetches the user profile from the user table by using the user's ID, as provided by request.user.id
, using the following SQL query:
SELECT * FROM user WHERE id = {request.user.id};
The profile is serialized using either the CandidateDetailsProfileSerializer
or EmployerDetailsProfileSerializer
, depending on the user_type
.
Returns the authenticated user's profile data, including relevant details based on their user_type
.
{
"data": {
"id": 3,
"email": "user1@gmail.com",
"contract_number": "01711111111",
"user_type": "candidate",
"candidate": {
"id": 1,
"profile_details": {
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Abc",
"address": "123 Main St, Springfield, USA",
"profile_pic": "/documents/profile_pictures/8.jpeg",
"bio": "A highly motivated software engineer with a passion for developing innovative programs.",
"social_links": [
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe"
],
"gender": "male",
"birth_date": "1995-06-15",
"user": 3
}
}
}
{
"data": {
"id": 2,
"email": "user2@gmail.com",
"contract_number": "017111111112",
"user_type": "employer",
"employer": {
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
}
}
}
If the user is not found or there is an error, a 404 Not Found response is returned, with the error message.
PATCH {{base_url}}/account/update-password/
Open to authenticated users.
Allows the authenticated user to change their password by providing the old password and new password.
The password is updated by validating the old password and checking if the new password matches the confirmation. The query involves checking the current password of the user with the old password and then updating the password with the new one. SQL query for updating the password:
UPDATE users
SET password = {new_password}
WHERE id = {request.user.id} AND password = {old_password};
The ChangePasswordSerializer
is used for validating the following fields:
- Validation Logic:
- Check if the password and
confirm_password
match. - Validates
old_password
to ensure it matches the current password.
- Check if the password and
- Password Update:
update()
sets the new password usingset_password()
and saves the user instance.
- Success & Error Responses:
- Returns 200 OK on successful change with a success message.
- Returns 400 BAD REQUEST if validation fails with detailed errors.
{
"old_password": "1234",
"password": "qwer0987",
"confirm_password": "qwer0987"
}
Returns a success message if the password change is successful.
{
"detail": "Password Change Successfully"
}
Returns validation errors if any of the fields are invalid.
{
"old_password": {
"old_password": "Old password is incorrect"
}
}
{
"password": [
"Password field does not match"
]
}
Candidate Features
Endpoint:
POST {{base_url}}/candidate/profile/
Permission:
Only accessible by candidates; restricted by the IsCandidate
permission class.
Function:
Updates the candidate profile based on the provided data. The profile is identified by the authenticated user's ID.
Query:
Fetches the CandidateProfile
for the authenticated user using the following PostgreSQL query:
SELECT * FROM candidate_profile WHERE user_id = request.user.id;
Serialization:
The provided data is deserialized into a CandidateProfile
instance using the CandidateSerializer
.
Request Body:
The request body should contain the fields to update the candidate profile:
{
"candidate": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
]
}
Response 200 (OK):
Returns the updated candidate profile data if the update is successful.
{
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
}
Response 400 (Bad Request):
Returns validation errors if the provided data is invalid.
Response 404 (Not Found):
Returns an error message if the candidate profile for the authenticated user is not found.
Response 500 (Internal Server Error):
Catches unexpected errors and returns a generic error message.
Endpoint:
GET {{base_url}}/candidate/profiles/
Authentication:
Custom authentication handled by CustomAuthentication
.
Permission:
Open to all users; no permission restrictions.
Function:
Fetches all candidate profiles.
Query:
Fetches all candidate profiles using the following PostgreSQL query:
SELECT * FROM candidate_profile;
Serialization:
The candidate profiles are serialized using the CandidateSerializer
, which includes a nested CandidateSkillsSerializer
to handle the serialization of the skills associated with each candidate.
Response 200 (OK):
Returns the list of all candidate profiles.
[
{
"id": 3,
"email": "user1@gmail.com",
"contract_number": "01711111111",
"user_type": "candidate",
"candidate": {
"id": 1,
"profile_details": {
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Abc",
"address": "123 Main St, Springfield, USA",
"profile_pic": "/documents/profile_pictures/8.jpeg",
"bio": "A highly motivated software engineer with a passion for developing innovative programs.",
"social_links": [
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe"
],
"gender": "male",
"birth_date": "1995-06-15",
"user": 3
}
},
{
"id": 4,
"email": "user3@gmail.com",
"contract_number": "01711111113",
"user_type": "candidate",
"candidate": {
"id": 2,
"profile_details": null,
"first_name": null,
"last_name": null,
"address": null,
"profile_pic": null,
"bio": null,
"social_links": null,
"gender": null,
"birth_date": null,
"user": 4
}
},
{
"id": 5,
"email": "user4@gmail.com",
"contract_number": "01711111114",
"user_type": "candidate",
"candidate": {
"id": 3,
"profile_details": null,
"first_name": null,
"last_name": null,
"address": null,
"profile_pic": null,
"bio": null,
"social_links": null,
"gender": null,
"birth_date": null,
"user": 5
}
}
]
Response 400 (Bad Request):
Returns an error message if an unexpected error occurs.
Endpoint:
GET {{base_url}}/candidate/profile/{candidate_id}/
Permission:
Open to all authenticated users.
Function:
Fetches the candidate profile based on the provided candidate_id
.
Query:
Fetches the CandidateProfile
by candidate_id
using the following PostgreSQL query:
SELECT * FROM candidate_profile WHERE id = candidate_id;
Serialization:
The candidate profile is serialized using the CandidateSerializer
, which includes a nested CandidateSkillsSerializer
for the skills field.
Response 200 (OK):
Returns the candidate profile data if found.
{
"id": 3,
"email": "user1@gmail.com",
"contract_number": "01711111111",
"user_type": "candidate",
"candidate": {
"id": 1,
"profile_details": {
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Abc",
"address": "123 Main St, Springfield, USA",
"profile_pic": "/documents/profile_pictures/8.jpeg",
"bio": "A highly motivated software engineer with a passion for developing innovative programs.",
"social_links": [
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe"
],
"gender": "male",
"birth_date": "1995-06-15",
"user": 3
}
}
Response 404 (Not Found):
Returns an error message if the profile does not exist.
Response 500 (Internal Server Error):
Catches unexpected errors and returns a generic error message.
GET {{base_url}}/candidate/application/list/
Restricted to authenticated candidates, enforced by IsCandidate
permission class.
Fetches the total number of job applications submitted by the authenticated candidate and returns the application data.
Fetches job applications for the authenticated candidate using the following PostgreSQL query:
SELECT * FROM job_application WHERE candidate_id = request.user.candidate.id;
request.user.candidate.id
refers to the id of the CandidateProfile
associated with the currently authenticated user.
The job applications are serialized using the JobApplicationSerializer
.
Returns the total number of applications and the data for each application submitted by the candidate.
{
"Total_Application": 3,
"data": [
{
"id": 1,
"application_date": "2024-11-09T15:33:42.201652Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": null,
"expected_salary": 75000.0,
"notice_period": "1 month",
"job": 3,
"candidate": 1
},
{
"id": 2,
"application_date": "2024-11-09T15:56:28.409689Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": null,
"expected_salary": 75000.0,
"notice_period": "1 month",
"job": 4,
"candidate": 1
},
{
"id": 3,
"application_date": "2024-11-09T16:02:09.322095Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": null,
"expected_salary": 75000.0,
"notice_period": "1 month",
"job": 5,
"candidate": 1
}
]
}
Returns validation errors or any issues with the request.
Catches unexpected errors and returns a generic error message.
GET {{base_url}}/employer/profile/{employer_id}/
Open to all users; no permission restrictions.
Fetches the employer profile based on the provided employer_id
.
Fetches the EmployerProfile
by employer_id
using the following PostgreSQL query:
SELECT * FROM employer_profile WHERE id = employer_id;
Converts the EmployerProfile
instance into JSON format using the EmployerProfileSerializer
.
Returns the employer profile data if found.
{
"id": 2,
"first_name": "Mr.",
"last_name": "KKD",
"address": "Dhaka, Bangladesh",
"website": "https://www.kkd.ltd.com",
"company_name": "Kazi & Kong Solutions Ltd",
"profile_picture": null,
"user": 6
}
Returns an error message if the profile does not exist.
Catches unexpected errors and returns a generic error message.
GET {{base_url}}/employer/profiles/
Open to all users; no permission restrictions.
Fetches all employer profiles.
Fetches all EmployerProfiles
using the following PostgreSQL query:
SELECT * FROM employer_profile;
Converts the EmployerProfile
instances into JSON format using the EmployerProfileSerializer
.
Returns a list of employer profile data.
[
{
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
},
{
"id": 2,
"first_name": "Mr.",
"last_name": "KKD",
"address": "Dhaka, Bangladesh",
"website": "https://www.kkd.ltd.com",
"company_name": "Kazi & Kong Solutions Ltd",
"profile_picture": null,
"user": 6
}
]
Returns an error message if the profile does not exist.
Catches unexpected errors and returns a generic error message.
PUT {{base_url}}/employer/profile/
Only accessible by employers; restricted by the IsEmployer
permission class.
Updates the employer profile based on the provided data. The profile is identified by the authenticated user's ID.
Fetches the EmployerProfile
for the authenticated user using the following PostgreSQL query:
SELECT * FROM employer_profile WHERE user_id = request.user.id;
Converts the provided data into an EmployerProfile
instance and updates the profile using the EmployerProfileSerializer
.
The request body should contain the following fields to update the employer profile:
{
"user": 6,
"first_name": "Mr.",
"last_name": "KKD",
"address": "Gulshan, Dhaka, Bangladesh",
"website": "https://www.kkd.ltd.com",
"company_name": "Kazi & Kong Solutions Ltd"
}
Returns the updated employer profile data if the update is successful.
{
"id": 2,
"first_name": "Mr.",
"last_name": "KKD",
"address": "Gulshan, Dhaka, Bangladesh",
"website": "https://www.kkd.ltd.com",
"company_name": "Kazi & Kong Solutions Ltd",
"profile_picture": null,
"user": 6
}
Returns validation errors if the provided data is invalid.
Returns an error message if the employer profile for the authenticated user is not found.
Catches unexpected errors and returns a generic error message.
POST {{base_url}}/job/manage/
Only accessible by employers; restricted by the IsEmployer
permission class.
Creates a new job using the provided data. The request is authenticated and validated before creating the job.
Inserts a new job into the database using the following PostgreSQL query:
INSERT INTO job (employer_id, title, description, job_type, job_subtype, experience_level, company_name, location, salary_range, posted_at, deadline, application_link, application_email, is_deleted) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, NOW(), %s, %s, %s, %s);
The provided data is deserialized into a Job
instance using the JobCreateSerializer
.
The request body should contain the following fields to create a job:
{
"employer": 2,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"deadline": "2024-12-31T23:59:59",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com"
}
Returns the created job data if the creation is successful.
Returns validation errors if the provided data is invalid.
Catches unexpected errors and returns a generic error message.
PATCH {{base_url}}/jobs/manage/{job_id}/
Only accessible by employers; restricted by the IsEmployer
permission class.
Updates the details of an existing job using the provided data. The job is identified by job_id
and is restricted to the authenticated employer.
Fetches the job to update using the following PostgreSQL query:
SELECT * FROM job WHERE id = {job_id} AND employer_id = (SELECT id FROM employer_profile WHERE user_id = {request.user.id});
The provided data is deserialized into the Job
instance using the JobSerializer
.
The request body should contain the fields to update the job:
{
"title": "Sir Software Engineer",
"description": "Develop.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Senior",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"deadline": "2024-12-31T23:59:59",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com"
}
Returns the updated job data if the update is successful.
{
"id": 10,
"title": "Sir Software Engineer",
"description": "Develop.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Senior",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-16T15:53:28.423515Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 2
}
Returns validation errors if the provided data is invalid.
Returns an error message if the job with the given job_id
for the authenticated employer is not found.
Catches unexpected errors and returns a generic error message.
Endpoint
PUT {{base_url}}/job/job_id/
Permission
Only accessible by employers; restricted by the IsEmployer
permission class.
Function
Deletes an existing job by marking it as deleted (is_deleted = True
). The job is identified by job_id
and must belong to the authenticated employer.
Query
Fetches the job to delete using the following PostgreSQL query:
SELECT * FROM job WHERE id = {job_id} AND employer_id = (SELECT id FROM employer_profile WHERE user_id = {request.user.id});
Response 200 (OK)
Indicates that the job has been successfully marked as deleted.
{
"error": "Job is deleted.",
"data": {
"id": 8,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-16T15:42:41.738041Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": true,
"employer": 2
}
}
Response 400 (Bad Request)
Returns an error message if the job is already marked as deleted or if the authenticated user is not authorized to delete the job.
Response 404 (Not Found)
Returns an error message if the job with the given job_id
for the authenticated employer is not found.
Response 500 (Internal Server Error)
Catches unexpected errors and returns a generic error message.
Endpoint
GET {{base_url}}/job/
Permission
Only accessible by employers; restricted by the IsEmployer
permission class.
Function
Fetches all active jobs (is_deleted = False
) created by the authenticated employer.
Query
Retrieves the employer's jobs using the following PostgreSQL query:
SELECT * FROM job WHERE is_deleted = FALSE AND employer_id = (SELECT id FROM employer_profile WHERE user_id = {request.user.id});
Serialization
The retrieved Job instances are serialized into JSON format using the JobSerializer
. This serializer includes a nested EmployerProfileSerializer
, which retrieves and includes information about the employer (the user who created the job).
Response 200 (OK)
Returns the total number of jobs and a list of job details if jobs are found.
{
"id": 3,
"employer": {
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
},
"total_applications": 1,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-09T14:33:14.149197Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false
}
Response 404 (Not Found)
Returns an error message if no jobs are found for the authenticated employer.
Response 500 (Internal Server Error)
Catches unexpected errors and returns a generic error message.
Endpoint
GET {{base_url}}/job/search/?title=Software Engineer
Permission
Requires authentication via the CustomAuthentication
class. Accessible by authorized users with the appropriate permissions.
Function
Filters and retrieves job listings. Applies the filtering logic using DjangoFilterBackend
and custom filter logic defined in the JobFilter
.
Query
Fetches jobs using the following query:
SELECT * FROM job WHERE is_deleted = FALSE;
Then, applies additional filtering based on the query parameters, such as job type, experience level, etc. This is done by the filter_queryset
method in the DjangoFilterBackend
class.
Serialization
The retrieved Job instances are serialized into JSON format using the JobDetailSerializer
. The serializer includes details about the employer who created the job and the total number of applications for each job.
- Utilize Django Filters: Set up
DjangoFilterBackend
and defined aJobFilter
class with customizable filters for fields like title, job_type, job_subtype, experience_level, and location for flexible search options. - Filter Application: Applied filtering logic in the
get()
method by usingDjangoFilterBackend().filter_queryset()
to refine job listings based on user queries. - Implement Pagination: Used
PageNumberPagination
with apage_size
of 10, allowing segmented data retrieval and improving load times for users. - Serialize Paginated Data: Used
JobDetailSerializer
to convert paginated job data into JSON format for the response, making the data easily consumable. - Handle No Results Gracefully: Added error handling to return a 404 response when no jobs match the filter criteria.
- Modular and Configurable Settings: Configured
REST_FRAMEWORK
settings to include the default authentication and filter backends, as well as pagination, enabling consistency and code modularity.
Response 200 (OK)
Returns a list of jobs with pagination and the total number of jobs after applying the filters.
{
"total_jobs": 2,
"data": [
{
"id": 3,
"employer": {
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
},
"total_applications": 1,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-09T14:33:14.149197Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false
},
{
"id": 9,
"employer": {
"id": 1,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 2
},
"total_applications": 0,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-16T15:44:41.140585Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false
}
]
}
Response 400 (Bad Request)
Returns validation errors if the request contains invalid or missing parameters.
Response 404 (Not Found)
Returns an error message if no jobs are found matching the criteria.
Response 500 (Internal Server Error)
Catches unexpected errors and returns a generic error message.
Endpoints
GET {{base_url}}/job/
GET {{base_url}}/job/{job_id}/
Permission
Only accessible by authenticated users with proper permissions, as determined by the CustomAuthentication
class.
Function
Fetches details of a specific job by its ID. The job's details are serialized, and the total number of applications for the job is calculated.
Query
Fetches the job details using the following query:
SELECT * FROM job WHERE id = job_id AND is_deleted = FALSE;
Then, it uses the JobDetailSerializer
to serialize the job data and also includes the total number of applications for the job.
Serialization
The retrieved Job instance is serialized into JSON format using the JobDetailSerializer
. This includes the job details and the total number of applications for that job, calculated by the get_total_applications
method.
Response 200 (OK)
Returns the detailed job data along with the total number of applications for the job.
{
"Total Job": 2,
"jobs": [
{
"id": 8,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-16T15:42:41.738041Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 2
},
{
"id": 10,
"title": "Sir Software Engineer",
"description": "Develop.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Senior",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-16T15:53:28.423515Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 2
}
]
}
Response 400 (Bad Request)
If the request contains invalid data, this response is returned.
Response 404 (Not Found)
If no job is found for the given job ID, this error is returned.
Response 500 (Internal Server Error)
Catches unexpected errors and returns a generic error message.
Applications Management
GET {{base_url}}/application/all/3/
Only accessible by authenticated employers who created the job, as determined by the IsEmployer
permission class.
Fetches all job applications for a specific job and provides the job application details, including job and candidate information. It uses caching to improve performance.
Fetches job applications using the query:
SELECT * FROM job_application WHERE job_id = job_id AND is_deleted = FALSE;
The response data is cached with a cache key formatted as job_applications_{job_id}
. If the job applications are already cached, the cached data is returned immediately, improving response times. If the job applications are not cached, they are fetched from the database, serialized, and cached for future use.
The retrieved job applications are serialized into JSON format using the JobApplicationCandidateDetailsSerializer
. This serializer includes nested serializers to provide detailed information about the job and candidate.
- The
job
field uses theJobSerializer
to include details about the job that was applied for. - The
candidate
field uses theCandidateSerializer
to include information about the candidate who applied.
No request body is required for this endpoint as it retrieves job applications.
Returns the list of job applications for the given job. If the data is cached, it is returned directly from the cache. If not cached, the applications are retrieved from the database, serialized, and cached.
{
"data": [
{
"id": 1,
"job": {
"id": 3,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-09T14:33:14.149197Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 1
},
"candidate": {
"id": 1,
"profile_details": {
"id": 1,
"resume": "/documents/resumes/Profile.pdf",
"education": [
"Bachelor of Science in Computer Science - University of Springfield",
"Master of Science in Software Engineering - Tech University"
],
"experience": [
"Software Developer at ABC Corp (2018-2021)",
"Lead Developer at XYZ Ltd (2021-Present)"
],
"skills": [
"Python",
"Django",
"SQL"
],
"languages": [
"English",
"Bangla"
],
"projects": [
"E-commerce website development",
"Inventory management system"
],
"certificate": [
"Certified Python Developer",
"AWS Certified Solutions Architect"
],
"awards": [
"Employee of the Year 2020",
"Best Innovation Award 2021"
],
"club_and_committee": [
"Tech Club President",
"Open Source Committee Member"
],
"Competitive_exams": [
"GRE - Score: 320",
"TOEFL - Score: 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Abc",
"address": "123 Main St, Springfield, USA",
"profile_pic": "/documents/profile_pictures/8_1u3Sq8J.jpeg",
"bio": "A highly motivated software engineer with a passion for developing innovative programs.",
"social_links": [
"https://linkedin.com/in/johndoe",
"https://github.com/johndoe"
],
"gender": "male",
"birth_date": "1995-06-15",
"user": 3
},
"application_date": "2024-11-09T15:33:42.201652Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": null,
"expected_salary": 75000.0,
"notice_period": "1 month"
},
{
"id": 4,
"job": {
"id": 3,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-09T14:33:14.149197Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 1
},
"candidate": {
"id": 2,
"profile_details": null,
"first_name": null,
"last_name": null,
"address": null,
"profile_pic": null,
"bio": null,
"social_links": null,
"gender": null,
"birth_date": null,
"user": 4
},
"application_date": "2024-11-16T16:44:48.978237Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": "/documents/resumes/Profile_u2RvErU.pdf",
"expected_salary": 75000.0,
"notice_period": "1 month"
},
{
"id": 5,
"job": {
"id": 3,
"title": "Software Engineer",
"description": "Develop and maintain web applications.",
"job_type": "Private",
"job_subtype": "Full-time",
"experience_level": "Mid",
"company_name": "Tech Solutions Ltd.",
"location": "New York, NY",
"salary_range": "70,000 - 90,000 USD",
"posted_at": "2024-11-09T14:33:14.149197Z",
"deadline": "2024-12-31T23:59:59Z",
"application_link": "https://techsolutions.com/careers/software-engineer",
"application_email": "jobs@techsolutions.com",
"is_deleted": false,
"employer": 1
},
"candidate": {
"id": 2,
"profile_details": null,
"first_name": null,
"last_name": null,
"address": null,
"profile_pic": null,
"bio": null,
"social_links": null,
"gender": null,
"birth_date": null,
"user": 4
},
"application_date": "2024-11-16T16:46:44.704249Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": "/documents/resumes/Profile_nhQlDo2.pdf",
"expected_salary": 75000.0,
"notice_period": "1 month"
}
]
}
If the user is not authorized to view the job applications or the request is invalid, this response is returned.
If no job application data is found for the given job ID, this error is returned.
Catches unexpected errors and returns a generic error message.
POST {{base_url}}/application/job_id/
Only accessible by authenticated candidates, as determined by the IsCandidate
permission class.
This endpoint allows candidates to apply for a job. Before creating a new application, the system checks if the candidate has already applied for the same job by querying the cache. If a cached result is found indicating the candidate has already applied, a response with the status "Already Applied" is returned. If no cached result is found, the system checks the database for any existing application. If the candidate has already applied, the result is cached, and the "Already Applied" response is returned. Otherwise, a new application is created, and the data is serialized and saved.
The query checks if the candidate has already applied to the job:
SELECT * FROM job_application WHERE candidate_id = {user_id} AND job_id = {job_id};
The result of the application check is cached using the key job_application_{user_id}_{job_id}
. If the candidate has already applied, the cache will store the response, and subsequent requests will return the cached result. If the candidate has not applied, a new job application is created, and the result is cached for future requests.
The data is serialized into JSON format using the JobApplicationSerializer
. This serializer includes all relevant job application details, such as candidate and job information.
The request body must include the necessary details to create a job application. It typically includes information about the candidate and the job they are applying for.
{
"job": 3,
"candidate": 2,
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": "path/to/resume.pdf",
"expected_salary": 75000.00,
"notice_period": "1 month"
}
If the candidate has already applied for the job, a response with the following message is returned:
{
"status": "Already Applied"
}
If the job application is successfully created, the response contains the serialized data of the new job application.
{
"id": 4,
"application_date": "2024-11-16T16:44:48.978237Z",
"status": "applied",
"cover_letter": "I am very excited to apply for the Software Engineer role. I believe my skills and experience align perfectly with the requirements for this position.",
"resume": "/documents/resumes/Profile_u2RvErU.pdf",
"expected_salary": 75000.0,
"notice_period": "1 month",
"job": 3,
"candidate": 2
}
If the request data is invalid or the job application fails validation, this response returns the serializer's validation errors.
If the job ID does not exist in the database, this error will be returned with a message indicating that the job could not be found.
Catches unexpected errors and returns a generic error message.
PATCH {{base_url}}/application/status/application_id/
Only accessible by authenticated employers who created the job, as determined by the IsEmployer
permission class.
Updates the status of a specific job application. This view ensures that only the employer who posted the job can update the application status. The status choices are limited to predefined options: "applied", "selected", "interviewed", "offered", and "rejected". Caching is used to optimize future requests for the same application status.
The query to update the application status in the database is:
UPDATE job_application
SET status = {new_status}
WHERE id = {application_id}
AND job_id = {job_id};
The status field is updated based on the new value provided in the request. The allowed values for status are drawn from the STATUS_CHOICES
field: 'applied', 'selected', 'interviewed', 'offered', and 'rejected'.
When the employer updates the status:
- The application is retrieved from the database using the
application_id
. - The employer's identity is verified to ensure they created the job for which the application was submitted.
- The status of the job application is updated based on the provided status in the request data, and the change is saved to the database.
The response data is cached with a cache key formatted as job_application_status_{application_id}
. If the status is updated, the new status is saved and cached for future use. If a cached status exists, it is updated to reflect the latest change.
The status update process focuses only on the status field, so no full serialization of the job application is required. The status is validated against STATUS_CHOICES
.
The request body should contain the status to be updated. The allowed values are:
- "applied"
- "selected"
- "interviewed"
- "offered"
- "rejected"
{
"status": "selected"
}
Returns a success message indicating that the job application's status was successfully updated. The new status is also cached for future requests.
{
"message": "Job Application's Status is updated."
}
If the employer is not authorized to update the job application status or if the status provided is invalid (not one of the STATUS_CHOICES
), this response is returned with a message indicating that the update is not allowed.
If no job application is found with the given application_id
, this error is returned with an error message.
Catches unexpected errors and returns a generic error message.
GET {{base_url}}/application/status/application_id/
Only accessible by authenticated employers who created the job, as determined by the IsEmployer
permission class.
Fetches all job applications for a specific job and provides the details of each job application, including job and candidate information. Caching is utilized to improve performance and reduce the database load for repeated requests.
The query to fetch the job applications from the database:
SELECT * FROM job_application WHERE job_id = {job_id};
The query retrieves all job applications for the specific job identified by job_id
.
The response data is cached with a cache key formatted as job_applications_{job_id}
. If the job applications are already cached, the cached data is returned immediately to improve response time. If the job applications are not cached, they are fetched from the database, serialized, and then cached for future requests.
The job applications are serialized into JSON format using the JobApplicationCandidateDetailsSerializer
. This includes job details, candidate information, and any other application-related data.
Returns a list of job applications for the specified job. The data is either returned from the cache if it exists or fetched from the database and cached for future use.
{
"data": {
"id": 1,
"job": {
"id": 1140,
"employer": {
"id": 1255,
"first_name": "Alice",
"last_name": "Smith",
"address": "456 Elm St, Chittagong, Bangladesh",
"website": "https://www.alicesmithco.com",
"company_name": "Alice Smith Solutions",
"profile_picture": null,
"user": 1833
},
"title": "full-stack Developer",
"description": "Design and implement user interface components for web applications.",
"job_type": "Private",
"job_subtype": "Contract",
"experience_level": "Entry",
"company_name": "Creative Code Inc.",
"location": "San Francisco, CA",
"salary_range": "5500000.0",
"posted_at": "2024-10-28T08:35:46.569199Z",
"deadline": "2024-11-15T23:59:59Z",
"application_link": "https://creativecode.com/careers/frontend-developer",
"application_email": "careers@creativecode.com",
"is_deleted": false
},
"candidate": {
"id": 1,
"skills": {
"id": 1,
"resume": null,
"education": [
"Bachelor of Science in Computer Science from XYZ University",
"High School Diploma from ABC High School"
],
"experience": [
"Software Engineer at Tech Solutions Ltd. (2 years)",
"Intern at Web Innovators Inc. (6 months)"
],
"skills": [
"Python",
"Django",
"REST APIs",
"JavaScript",
"React.js",
"SQL"
],
"languages": [
"English",
"Spanish"
],
"projects": [
"Developed an E-commerce website using Django and React",
"Built a microservices architecture for a financial application"
],
"certificate": [
"Certified AWS Solutions Architect",
"Microsoft Certified Azure Developer"
],
"awards": [
"Employee of the Month at Tech Solutions Ltd.",
"Dean's List at XYZ University"
],
"club_and_committee": [
"Member of the Coding Club at XYZ University",
"Volunteer at Local Community Center"
],
"Competitive_exams": [
"GRE with a score of 320",
"TOEFL with a score of 110"
],
"candidate": 1
},
"first_name": "Mr",
"last_name": "Ten",
"address": "Dhaka, Bangladesh",
"profile_pic": "/documents/profile_pictures/images_NIMZ6NU.jpeg",
"bio": "Software engineer with 2 years of experience in full-stack development.",
"social_links": [
"https://www.linkedin.com/in/mrten",
"https://github.com/mrten"
],
"gender": "male",
"birth_date": "1999-01-19",
"user": 1837
},
"application_date": "2024-10-28T09:23:31.506082Z",
"status": "Selected",
"cover_letter": "I am excited to apply for this position as I believe my skills match the job requirements.",
"resume": null,
"accepted_salary": null,
"notice_period": null
}
}
If the user is not authorized to view the job applications or if the request is invalid, this response is returned.
If no job application data is found for the given job ID, this error is returned.
Catches unexpected errors and returns a generic error message.
POST {{base_url}}/subscription/plan/
Only accessible by authenticated admins, as determined by the IsAdmin
permission class.
Creates a new subscription plan by accepting the subscription data in the request body. If the data is valid, it is saved, and the subscription plan is created. If the data is invalid, it returns the corresponding error message.
The subscription data is inserted into the database with a query like:
INSERT INTO subscription_plan (plan_name, duration, price, features)
VALUES ({plan_name}, {duration}, {price}, {features});
The request data is serialized using the SubscriptionSerializer
, which ensures that the incoming data matches the structure of the SubscriptionPlan
model.
The body of the request should contain the subscription plan details. This is validated by the SubscriptionSerializer
.
{
"id": 7,
"name": "free",
"price": "0.00",
"description": "Free plan with basic access to job portal features.",
"features": "Limited job applications, No priority support, No career coaching",
"duration": "00:00:00",
"max_job_applications_per_day": 5,
"priority_support": false,
"career_coaching": false,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:54:38.517365Z"
}
If the subscription plan is successfully created, a response containing the newly created subscription plan data is returned with a 201 Created status.
{
"detail": "Password Change Successfully"
}
If the request data is invalid or does not meet the model’s requirements, a 400 Bad Request response with the validation errors is returned.
GET {{base_url}}/subscription/plan/
Accessible by authenticated users (permissions can be adjusted as needed, based on the use case).
Fetches and returns all subscription plans from the database. It retrieves the subscription data and serializes it for the response.
The query to fetch all subscription plans from the database:
SELECT * FROM subscription_plan;
The retrieved subscription plans are serialized into JSON format using the SubscriptionSerializer
.
Returns the list of all subscription plans in the database, serialized in JSON format.
[
{
"id": 7,
"name": "free",
"price": "0.00",
"description": "Free plan with basic access to job portal features.",
"features": "Limited job applications, No priority support, No career coaching",
"duration": "00:00:00",
"max_job_applications_per_day": 5,
"priority_support": false,
"career_coaching": false,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:54:38.517365Z"
},
{
"id": 8,
"name": "basic",
"price": "9.99",
"description": "Basic plan with access to essential features.",
"features": "10 job applications per day, No priority support, No career coaching",
"duration": "30 00:00:00",
"max_job_applications_per_day": 10,
"priority_support": false,
"career_coaching": false,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:54:50.480339Z"
},
{
"id": 9,
"name": "professional",
"price": "49.99",
"description": "Professional plan with advanced features.",
"features": "20 job applications per day, Priority support, Career coaching",
"duration": "30 00:00:00",
"max_job_applications_per_day": 20,
"priority_support": true,
"career_coaching": true,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:55:02.362213Z"
},
{
"id": 10,
"name": "premium",
"price": "99.99",
"description": "Premium plan with exclusive features.",
"features": "50 job applications per day, Priority support, Career coaching, Resume review",
"duration": "30 00:00:00",
"max_job_applications_per_day": 50,
"priority_support": true,
"career_coaching": true,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:55:13.833878Z"
},
{
"id": 11,
"name": "enterprise",
"price": "199.99",
"description": "Enterprise plan for large organizations with all-inclusive features.",
"features": "Unlimited job applications per day, 24/7 priority support, Career coaching, Resume review, Analytics",
"duration": "30 00:00:00",
"max_job_applications_per_day": 100,
"priority_support": true,
"career_coaching": true,
"create_at": "2024-11-10T12:00:00Z",
"update_at": "2024-11-10T07:55:23.958759Z"
}
]
In case of an unexpected error (such as a database error or general failure), this response is returned with an error message.
POST {{base_url}}/subscription/add/plan/
Accessible by authenticated users (the CustomAuthentication
class is used for authentication).
Allows users to choose a subscription plan and save it in the database. The subscription details are validated and stored for the user.
The method checks if the plan is provided and the trial_start_date
is not set.
If the conditions are met, it calculates the trial_start_date
by adding the plan's duration to the start_date
field.
After the necessary validation and modification, the method saves the subscription details to the database by calling super().save()
.
The query to insert the chosen subscription plan into the database (assuming it's stored in a UserSubscription
model):
INSERT INTO user_subscription (user_id, subscription_plan_id, start_date, end_date, status)
VALUES ({user_id}, {subscription_plan_id}, {start_date}, {end_date}, {status});
The provided subscription data is serialized into the UserSubscription
model using the SubscriptionPlanSerializer
.
The request body should contain the subscription details to be saved, including the subscription plan ID, start date, end date, and any other necessary data.
{
"user": 1837,
"plan": 8
}
Returns the data of the newly created subscription in JSON format.
{
"id": 3,
"start_date": "2024-11-19T09:06:02.359796Z",
"auto_renew": true,
"has_expired": false,
"is_active": true,
"trial_start_date": "2024-12-19T09:06:02.359796Z",
"created_at": "2024-11-19T09:06:02.359957Z",
"updated_at": "2024-11-19T09:06:02.359963Z",
"user": 1837,
"plan": 8
}
If the data is invalid or the subscription cannot be saved, this response will include error details.
PUT {{base_url}}/subscription/plan/{plan_id}/
Only accessible by authenticated admins, as determined by the IsAdmin
permission class.
Updates an existing subscription plan with the provided data in the request body. If the data is valid, the subscription plan is updated in the database. If the data is invalid, it returns the corresponding error message.
The query to update the subscription plan in the database:
UPDATE subscription_plan SET plan_name = {plan_name}, duration = {duration}, price = {price}, features = {features} WHERE id = {plan_id};
The request data is serialized using the SubscriptionSerializer
, which ensures that the incoming data matches the structure of the SubscriptionPlan
model.
The body of the request should contain the updated subscription plan details. This is validated by the SubscriptionSerializer
.
{
"name": "premium",
"price": "99.99",
"description": "Premium plan with exclusive features.",
"features": "50 job applications per day, Priority support, Career coaching, Resume review",
"duration": "30 00:00:00",
"max_job_applications_per_day": 50,
"priority_support": true,
"career_coaching": true
}
If the subscription plan is successfully updated, a response containing the updated subscription plan data is returned with a 200 OK status.
If the request data is invalid or does not meet the model’s requirements, a 400 Bad Request response with the validation errors is returned.
If the subscription plan with the specified ID does not exist, a 404 Not Found response is returned.
A cron job is a time-based job scheduler in Unix-like operating systems, which I use to run tasks automatically at specified intervals or times.
I use django-cron
, a third-party package, to integrate cron jobs into my Django project. I define the logic for the job in a class that inherits from CronJobBase
, and I specify a schedule (like every 10 minutes). The do()
method contains the code I want to run when the cron job executes.
In the cron job example, it runs every 10 minutes (Schedule(run_every_mins=10)
). It checks all active subscriptions (UserSubscription.objects.filter(is_active=True)
).
- If the trial date has passed and auto_renew is enabled:
- I update the
start_date
andtrial_start_date
.
- I update the
- If auto_renew is disabled:
- I mark the subscription as expired (
has_expired = True
) and inactive (is_active = False
).
- I mark the subscription as expired (
- I can automate recurring tasks.
- It saves me time and resources by handling background processes for me.
- I can customize the intervals (e.g., minutes, hours, days) to fit my needs.
Follow these steps to set up the project locally:
- Clone the repository:
https://github.com/touhid9teen/JobPortal
- Navigate to the project directory:
cd Django_Job_Portal
- Create a virtual environment:
python3 -m venv env
- Activate the virtual environment:
- On Windows:
.\env\Scripts\activate
- On macOS/Linux:
source env/bin/activate
- On Windows:
- Install the required dependencies:
pip install -r requirements.txt
- Apply the migrations:
python manage.py migrate
- Create a superuser:
python manage.py createsuperuser
- Run the development server:
python manage.py runserver
Open your browser and visit:
http://127.0.0.1:8000/
Log in using the superuser credentials created during setup. Explore the features of the Django Job Portal.
We welcome contributions! To get started:
- Fork the repository.
- Create a new branch:
git checkout -b feature-branch
- Make your changes and commit them:
git commit -m "Description of your changes"
- Push your changes to the branch:
git push origin feature-branch
- Open a pull request for review.
This project is licensed under the MIT License. See the LICENSE file for details.