尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django就业系统实战:PostgreSQL建模与Admin定制

Django就业系统实战:PostgreSQL建模与Admin定制 简介本资源是一套完整可用的大学生就业信息管理系统毕业设计项目面向计算机及相关专业本科生解决毕业设计选题难、系统开发无参考、数据库与前后端联调缺实操的问题。项目基于Python Django框架开发含全部可运行源码、SQLite3数据库文件及配套HTML/CSS/JS前端页面还包含多份CSV格式的就业数据样本如python_sum、算法_sum、前端_sum等和数据分析可视化模板如xinzi_bar_sum.html、job_demand_pie_sum.html便于拓展统计与预测功能。压缩包共80个文件涵盖20个核心Python模块models.py、views.py、urls.py等、17个结构化数据CSV、10个HTML页面、10个PNG图表图示及JS/CSS静态资源整体大小24.11MB目录组织规范含templates、static、migrations等标准Django结构。目前已有103人学习下载项目经导师指导并获98分高分评价所有代码均本地调试通过可直接部署运行适合毕业设计快速启动与Django全栈实战训练。1. 这不是又一个“增删改查”Demo用 Django 搭建真实可用的大学生就业信息管理系统覆盖从数据库建模、后台管理到前端展示的完整闭环很多同学拿到“基于 Python 的大学生就业信息管理系统”这个毕业设计题目时第一反应是抄个 CRUD 页面、硬塞几条 fake 数据、凑够 50 行 views.py 就交差。但现实里教务处要导出 Excel 统计各专业签约率辅导员需要按企业性质筛选实习岗位学生本人得上传 PDF 简历并查看投递状态——这些需求倒逼系统必须有规范的数据库关系、可扩展的权限控制、能落地的文件处理逻辑以及真正能跑在 Linux 服务器上的部署结构。本项目不是玩具它用 Django 4.2LTS 版本构建数据库采用 PostgreSQL兼顾事务与 JSON 字段支持所有模型严格遵循 ER 图设计原则学生表与企业表通过“就业记录”多对多关联岗位信息嵌套在企业下而非独立大表简历文件走django-storages抽象层预留 S3 兼容路径。源码结构清晰分层core/放通用工具job/是核心业务users/实现角色分离学生/辅导员/管理员连requirements.txt都标注了每个包的实际用途——比如python-decouple用于安全隔离 SECRET_KEYdjango-filter支持前端多条件联合筛选。适合计算机、信管、软工等专业本科生复现也足够作为初入职场的 Django 工程师练手项目。2. 用 Django 4.2 PostgreSQL 建模就业数据从 ER 图到迁移命令的完整链路2.1 为什么选 PostgreSQL 而非 SQLite三个硬性理由毕业设计系统一旦涉及多用户并发录入如上百名学生同时上传简历、需要按地域/薪资/行业做聚合统计、或未来要对接 BI 工具SQLite 的锁机制和缺失的 JSONB 类型就会成为瓶颈。PostgreSQL 提供原生 JSONB 字段存储企业联系人列表、岗位技能要求如[Python, Django, SQL]无需额外序列化行级锁 并发插入支持辅导员批量导入企业信息时不会阻塞学生提交简历开箱即用的全文检索对岗位描述字段执行search_vector索引比LIKE %关键词%快 10 倍以上。提示本地开发用docker run -d --name pg-job -e POSTGRES_PASSWORDdev123 -p 5432:5432 -v $(pwd)/pgdata:/var/lib/postgresql/data postgres:15-alpine启动容器避免 Windows 下 PostgreSQL 安装的路径陷阱。2.2 核心模型设计紧扣就业场景的 5 张表及其外键约束根据高校就业工作流程我们定义以下模型代码精简关键字段完整版见job/models.py# job/models.py from django.db import models from django.contrib.auth.models import User class Company(models.Model): name models.CharField(max_length100, verbose_name企业名称) industry models.CharField(max_length50, verbose_name所属行业) # 教育、IT、制造等 address models.TextField(verbose_name详细地址) contact_persons models.JSONField(defaultlist, verbose_name联系人列表) # [{name: 张经理, phone: 138...}] class JobPosition(models.Model): title models.CharField(max_length100, verbose_name岗位名称) salary_range models.CharField(max_length50, verbose_name薪资范围) # 6K-10K requirements models.JSONField(defaultlist, verbose_name任职要求) # [熟悉Django, 有实习经验] company models.ForeignKey(Company, on_deletemodels.CASCADE, related_namepositions) class Student(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) major models.CharField(max_length50, verbose_name专业) graduation_year models.IntegerField(verbose_name毕业年份) # 2025 resume_file models.FileField(upload_toresumes/%Y/%m/, verbose_name简历文件) class EmploymentRecord(models.Model): student models.ForeignKey(Student, on_deletemodels.CASCADE, related_namerecords) company models.ForeignKey(Company, on_deletemodels.PROTECT) # 企业删除时禁止级联 position models.ForeignKey(JobPosition, on_deletemodels.PROTECT) status models.CharField(max_length20, choices[ (OFFER, 已录用), (INTERVIEW, 面试中), (REJECTED, 未通过) ]) signed_date models.DateField(nullTrue, blankTrue, verbose_name签约日期)2.2.1 外键设计的关键取舍PROTECTvsCASCADEcompany和position字段使用on_deletemodels.PROTECT防止误删企业导致学生就业记录丢失删除前必须手动清空关联记录student字段用models.CASCADE学生账号注销时其所有就业记录自动清理符合数据生命周期管理。2.2.2 JSONField 的实际用法与验证contact_persons字段存的是标准 JSON 数组Django 自动序列化/反序列化。为防前端传入非法格式在CompanyAdmin中添加验证# job/admin.py from django.contrib import admin from .models import Company admin.register(Company) class CompanyAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): # 验证 contact_persons 是合法 JSON 列表 contacts obj.contact_persons if not isinstance(contacts, list): raise ValueError(联系人必须是列表格式) for c in contacts: if not isinstance(c, dict) or name not in c or phone not in c: raise ValueError(每个联系人需包含 name 和 phone 字段) super().save_model(request, obj, form, change)2.3 执行迁移从模型生成 SQL 到生产环境校验运行以下命令生成并应用迁移# 1. 生成迁移文件会自动检测模型变更 python manage.py makemigrations job users core # 2. 查看将执行的 SQL关键确认无 DROP TABLE python manage.py sqlmigrate job 0001_initial # 3. 应用迁移生产环境务必先备份数据库 python manage.py migrate # 4. 创建超级用户用于登录 admin 后台 python manage.py createsuperuser注意sqlmigrate输出中若出现DROP TABLE或ALTER COLUMN ... TYPE说明模型修改可能破坏数据需手动编写迁移脚本python manage.py makemigrations --empty job并填充RunPython操作。3. 用 Django Admin 实现零代码后台管理定制化列表页、搜索与导出功能3.1 让辅导员一眼看清关键数据重写 CompanyAdmin 的列表展示默认 admin 列表只显示__str__但辅导员需要快速识别企业规模、行业分布、联系人数量。我们在job/admin.py中定制from django.contrib import admin from django.urls import reverse from django.utils.html import format_html from .models import Company, JobPosition, Student, EmploymentRecord admin.register(Company) class CompanyAdmin(admin.ModelAdmin): list_display [name, industry, contact_count, position_count, created_at] list_filter [industry, created_at] # 右侧筛选栏 search_fields [name, address] # 顶部搜索框支持模糊匹配 date_hierarchy created_at # 按时间分层导航 def contact_count(self, obj): return len(obj.contact_persons) contact_count.short_description 联系人数 def position_count(self, obj): return obj.positions.count() position_count.short_description 岗位数3.1.1list_display中函数字段的性能优化position_count直接调用obj.positions.count()会导致 N1 查询。正确做法是预加载关联数据# 在 CompanyAdmin 中添加 def get_queryset(self, request): qs super().get_queryset(request) return qs.prefetch_related(positions) # 预加载所有岗位3.2 学生就业记录导出为 Excel用 openpyxl 实现一键下载Django admin 默认不支持导出我们通过自定义 action 实现# job/admin.py import openpyxl from io import BytesIO from django.http import HttpResponse def export_employment_records(modeladmin, request, queryset): # 创建 Excel 工作簿 wb openpyxl.Workbook() ws wb.active ws.title 就业记录导出 # 写入表头 headers [学生姓名, 专业, 企业名称, 岗位, 状态, 签约日期] for col_num, header in enumerate(headers, 1): ws.cell(row1, columncol_num, valueheader) # 写入数据使用 select_related 避免 N1 rows queryset.select_related( student__user, company, position ).values_list( student__user__first_name, student__major, company__name, position__title, status, signed_date ) for row_num, row in enumerate(rows, 2): for col_num, cell_value in enumerate(row, 1): ws.cell(rowrow_num, columncol_num, valuecell_value or ) # 返回响应 response HttpResponse(content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet) response[Content-Disposition] attachment; filenameemployment_records.xlsx wb.save(response) return response export_employment_records.short_description 导出选中的就业记录为 Excel # 在 EmploymentRecordAdmin 中注册 admin.register(EmploymentRecord) class EmploymentRecordAdmin(admin.ModelAdmin): actions [export_employment_records] list_display [student, company, position, status, signed_date] list_filter [status, company__industry] search_fields [student__user__first_name, company__name]提示导出功能需安装openpyxl3.1.0在requirements.txt中明确版本号避免因低版本不支持.xlsx格式导致报错。3.3 权限精细化控制区分学生、辅导员、管理员三类角色Django 默认只有is_staff和is_superuser我们通过Group实现角色分离# 初始化脚本scripts/create_groups.py from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType from job.models import Company, JobPosition, EmploymentRecord # 创建辅导员组 counselor_group, _ Group.objects.get_or_create(nameCounselor) # 授予企业、岗位、就业记录的增删改查权限 for model in [Company, JobPosition, EmploymentRecord]: content_type ContentType.objects.get_for_model(model) permissions Permission.objects.filter(content_typecontent_type) counselor_group.permissions.set(permissions) # 创建学生组仅能查看自己的记录 student_group, _ Group.objects.get_or_create(nameStudent) student_perm Permission.objects.get( codenameview_employmentrecord, content_typeContentType.objects.get_for_model(EmploymentRecord) ) student_group.permissions.add(student_perm)运行python manage.py runscript create_groups后在 admin 中为用户分配对应 Group 即可生效。4. 前端页面与文件上传用 Bootstrap 5 构建响应式就业信息页安全处理 PDF 简历4.1 学生个人中心页展示就业进度与上传入口创建job/templates/job/student_dashboard.html!-- job/templates/job/student_dashboard.html -- div classcontainer mt-4 h2我的就业进展/h2 !-- 当前状态卡片 -- div classrow mb-4 {% for record in student_records %} div classcol-md-4 mb-3 div classcard border-{{ record.status|lower }} div classcard-body h5 classcard-title{{ record.company.name }}/h5 p classcard-text{{ record.position.title }}/p span classbadge bg-{{ record.status|lower|default:secondary }} {{ record.get_status_display }} /span {% if record.signed_date %} p classmt-2签约日期{{ record.signed_date|date:Y-m-d }}/p {% endif %} /div /div /div {% endfor %} /div !-- 简历上传表单 -- div classcard div classcard-header上传最新简历/div div classcard-body form methodpost enctypemultipart/form-data {% csrf_token %} {{ form.as_p }} button typesubmit classbtn btn-primary上传简历/button /form /div /div /div对应视图job/views.pyfrom django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.core.files.storage import default_storage from django.core.files.base import ContentFile from .models import Student login_required def student_dashboard(request): student Student.objects.get(userrequest.user) records student.records.select_related(company, position).all() if request.method POST: resume_file request.FILES.get(resume_file) if resume_file and resume_file.name.endswith(.pdf): # 限制文件大小2MB if resume_file.size 2 * 1024 * 1024: return render(request, job/student_dashboard.html, { error: 简历文件不能超过 2MB, student_records: records }) # 保存到 media/resumes/YYYY/MM/ path default_storage.save( fresumes/{request.user.username}/{resume_file.name}, ContentFile(resume_file.read()) ) student.resume_file path student.save() return redirect(student_dashboard) return render(request, job/student_dashboard.html, { student_records: records })4.1.1 文件上传的安全加固点后缀白名单只允许.pdf拒绝.php、.exe等危险类型大小限制服务端校验resume_file.size避免前端 JS 校验被绕过路径隔离fresumes/{request.user.username}/...确保不同用户文件不互相覆盖。4.2 使用 Bootstrap 5 构建企业列表页响应式网格与搜索联动job/templates/job/company_list.htmldiv classcontainer mt-4 div classd-flex justify-content-between align-items-center mb-3 h2合作企业名录/h2 form classd-flex methodget input typetext nameq classform-control me-2 placeholder搜索企业名称或行业... value{{ request.GET.q }} button classbtn btn-outline-primary typesubmit搜索/button /form /div div classrow {% for company in companies %} div classcol-md-4 mb-4 div classcard h-100 div classcard-body h5 classcard-title{{ company.name }}/h5 p classtext-muted{{ company.industry }} | {{ company.address|truncatechars:30 }}/p pstrong联系人/strong {% for c in company.contact_persons|slice::2 %} {{ c.name }}({{ c.phone }}) {% endfor %} {% if company.contact_persons|length 2 %}{{ company.contact_persons|length|add:-2 }}位{% endif %} /p a href{% url company_detail company.id %} classbtn btn-sm btn-primary查看详情/a /div /div /div {% endfor %} /div /div对应视图支持搜索# job/views.py from django.db.models import Q def company_list(request): query request.GET.get(q, ) if query: companies Company.objects.filter( Q(name__icontainsquery) | Q(industry__icontainsquery) ) else: companies Company.objects.all() return render(request, job/company_list.html, {companies: companies})5. 生产环境部署与性能调优Nginx Gunicorn PostgreSQL 的最小可行配置5.1 用 Gunicorn 替代 runserver启动参数与进程管理runserver仅用于开发生产必须用 WSGI 服务器。安装并配置 Gunicornpip install gunicorn创建gunicorn.conf.py放在项目根目录import multiprocessing # 绑定设置 bind 127.0.0.1:8000 bind_ssl_certificate workers multiprocessing.cpu_count() * 2 1 worker_class sync worker_connections 1000 timeout 30 keepalive 2 # 日志 accesslog /var/log/myjob/gunicorn_access.log errorlog /var/log/myjob/gunicorn_error.log loglevel info启动命令# 后台运行生产环境必须加 --daemon gunicorn --config gunicorn.conf.py myjob.wsgi:application # 查看进程 ps aux | grep gunicorn提示workers数量按CPU核心数×21设置避免过多进程争抢内存timeout设为 30 秒防止慢查询拖垮服务。5.2 Nginx 反向代理配置静态文件托管与负载均衡预备/etc/nginx/sites-available/myjobupstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name your-domain.com; # 静态文件直接由 Nginx 服务提升 3 倍速度 location /static/ { alias /home/deploy/myjob/staticfiles/; expires 1y; add_header Cache-Control public, immutable; } location /media/ { alias /home/deploy/myjob/media/; expires 1y; } # 动态请求转发给 Gunicorn location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }启用站点并重载sudo ln -sf /etc/nginx/sites-available/myjob /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx5.3 数据库连接池优化用 pgBouncer 减少 PostgreSQL 连接开销Django 默认每次请求新建数据库连接高并发时易耗尽连接数。pgBouncer作为连接池中间件解决此问题# Ubuntu 安装 sudo apt install pgbouncer # 配置 /etc/pgbouncer/pgbouncer.ini [databases] myjob host127.0.0.1 port5432 dbnamemyjob [pgbouncer] listen_addr 127.0.0.1 listen_port 6432 auth_type md5 auth_file /etc/pgbouncer/userlist.txt pool_mode transactionDjangosettings.py修改数据库配置DATABASES { default: { ENGINE: django.db.backends.postgresql, NAME: myjob, USER: deploy, PASSWORD: your_password, HOST: 127.0.0.1, # 指向 pgbouncer PORT: 6432, # 不再是 5432 } }重启服务sudo systemctl restart pgbouncer sudo systemctl restart gunicorn5.3.1 验证连接池是否生效登录 PostgreSQL 执行SELECT * FROM pg_stat_activity WHERE client_addr 127.0.0.1;若看到大量连接来自pgbouncer进程而非 Django 进程说明连接复用成功。6. 毕业答辩必备技巧如何用 Django Shell 快速演示数据逻辑避开现场翻车6.1 用 shell 模拟真实业务场景三步验证系统健壮性答辩时最怕评委问“如果企业删除了学生的就业记录怎么处理”此时打开 Django shell 演示比口头解释更可信python manage.py shell# 步骤1创建测试数据 from job.models import Company, JobPosition, Student, EmploymentRecord from django.contrib.auth.models import User # 创建企业 comp Company.objects.create(name腾讯科技, industryIT, address深圳南山区) pos JobPosition.objects.create(title后端开发, salary_range15K-25K, companycomp) # 创建学生 user User.objects.create_user(usernamezhangsan, password123456) stu Student.objects.create(useruser, major计算机科学, graduation_year2025) # 创建就业记录 rec EmploymentRecord.objects.create( studentstu, companycomp, positionpos, statusOFFER ) # 步骤2尝试删除企业触发 PROTECT try: comp.delete() except Exception as e: print(f捕获异常{e}) # 输出Cannot delete some instances of model Company because they are referenced through a protected foreign key. # 步骤3正确删除流程先清空记录 rec.delete() comp.delete() # 成功 print(企业删除成功就业记录已提前清理)6.2 导出关键统计报表用 raw SQL 快速生成答辩 PPT 数据评委常问“各专业签约率是多少”用 Django ORM 写复杂聚合太慢直接执行 SQLfrom django.db import connection def get_major_stats(): with connection.cursor() as cursor: cursor.execute( SELECT s.major, COUNT(*) as total_students, COUNT(CASE WHEN er.status OFFER THEN 1 END) as offer_count, ROUND(COUNT(CASE WHEN er.status OFFER THEN 1 END)::DECIMAL * 100 / COUNT(*), 2) as rate FROM job_student s LEFT JOIN job_employmentrecord er ON s.id er.student_id GROUP BY s.major ORDER BY rate DESC ) return cursor.fetchall() # 在 shell 中调用 stats get_major_stats() for major, total, offer, rate in stats: print(f{major}: {offer}/{total} ({rate}%))输出示例计算机科学: 86/102 (84.31%) 软件工程: 72/95 (75.79%) 信息管理: 45/68 (66.18%)注意raw SQL中的表名job_student是 Django 自动生成的app名_模型小写可通过python manage.py dbshell连接数据库后执行\dt查看真实表名。6.3 检查数据库索引缺失用 EXPLAIN 分析慢查询根源当发现“按行业筛选企业”响应缓慢时在dbshell中执行EXPLAIN ANALYZE SELECT * FROM job_company WHERE industry IT;若输出中Seq Scan全表扫描占比高说明缺少索引-- 添加索引在 Django migration 中执行 CREATE INDEX idx_company_industry ON job_company (industry);然后再次EXPLAIN确认变为Index Scan响应时间从 1200ms 降至 8ms。指标优化前优化后查询耗时1200ms8ms扫描行数12,456237是否使用索引否是答辩时展示这张对比表比说“我做了优化”更有说服力。本文还有配套的精品资源点击获取
返回列表