Yesterday, I built the foundation of the DevBoard project: custom user model, two roles, authentication. Today, for Day 74, I built the employer side of DevBoard. By the end of today, an employer can log in, post a job, manage their listings, and see who has applied. The jobs app went from completely empty to fully functional on the employer side.
Starting with the Job Model
The first thing I did was define the data. Before writing a single view or template, I sat down and thought through what a job listing actually needs. Title, description, location, those are obvious. But a real job board needs more: job type (full-time, part-time, remote), experience level, salary range, tech stack, and an application deadline.
The model ended up with more fields than I initially expected, but every field serves a purpose. The tech stack is stored as a comma-separated string with a helper method to convert it to a list, the same pattern I used for candidate skills yesterday. The is_active flag lets employers pause a listing without deleting it, which felt like an important distinction.
The Application model was equally important to think through carefully. An application links a candidate to a job, carries the cover letter, and has a status: pending, reviewed, accepted, rejected. The status field uses Django's choices system, which means the admin panel and forms automatically get a dropdown instead of a free-text field. Small detail, big difference in usability.
One constraint I added: a candidate can only apply to the same job once. Django's unique_together in the Meta class handles this at the database level, not just in form validation.
The Employer Dashboard
The dashboard is the first thing an employer sees after logging in. I wanted it to be genuinely useful, not just a list of jobs, but a quick overview of what's happening across all their listings.
The view fetches all jobs belonging to the current employer and annotates each one with its application count. Django's ORM annotation system is perfect for this; instead of making a separate query for each job, a single query fetches jobs and counts in one go. This was the most interesting ORM work I've done so far in this project.
The dashboard template shows each job with its title, status, date posted, and how many applications it has received. Active and inactive listings are visually distinguished so employers can immediately see the state of their board.
All employer views are protected with the @employer_required decorator I wrote yesterday. A candidate who somehow lands on an employer URL gets redirected away immediately.
Posting a Job
The job creation form was straightforward to build, but required one decision: should the tech stack be a free text field or a set of checkboxes for predefined technologies? I went with free text for now, comma-separated, same as candidate skills. It's less polished but faster to build and easier to extend later.
The form uses commit=False before saving to attach the current employer as the author of the listing. This is the same pattern from the authentication day: never trust the form to set ownership, always set it in the view.
After a successful post, the employer is redirected back to their dashboard, where the new listing immediately appears. Small detail, but important for the user experience; you want confirmation that the action worked.
Editing and Deleting Jobs
Editing uses the same form as creation, pre-filled with the existing job data. The view includes an ownership check before rendering; if somehow a different employer tries to edit another employer's job by manipulating the URL, they get redirected away. The check is simple: compare job.employer to request.user. If they don't match, redirect.
Deleting follows the same ownership check pattern. I added a confirmation step in the template: a simple "are you sure?" message before the delete form submits. Deleting a job also deletes all applications for it through Django's CASCADE behavior on the ForeignKey, which is exactly the right behavior here.
Viewing Applications
This was the most satisfying part of today. Each job has a detail page accessible only to the employer who posted it, showing all applications received. Each application shows the candidate's name, the date they applied, their cover letter, and the current status.
The employer can update the status of any application; move it from pending to reviewed, or make a decision. This is handled by a small dedicated view that only accepts POST requests and only updates the status field, nothing else. Keeping the update scope narrow felt like the right call.
I also added a link from each application to the candidate's profile so employers can see skills, experience, GitHub, and portfolio without leaving the app.
What the Jobs App Looks Like Now
By the end of today, the jobs app has models, views, forms, URLs, and templates for everything an employer needs. The accounts app and jobs app are now talking to each other; the employer profile set up yesterday connects directly to job listings today.
One thing I noticed while building: the role-based decorator I wrote yesterday made today's views cleaner than expected. Every employer view has one line of protection at the top, then focuses on the actual job. Worth the upfront investment.
Where Things Stand After Day 74
Employers can now:
- See a dashboard with all their listings and application counts
- Post new jobs with full details
- Edit and delete their own listings
- View all applications for each job
- Update application status
Tomorrow: the candidate side: browsing jobs, searching and filtering, and applying.
Thanks for reading. Feel free to share your thoughts!
Top comments (0)