1.总体描述

1.1产品需求:

1.2职位管理系统-建模

1.3 Django项目代码结构

  • 新增recruitment项目
django-admin startproject recruitment
  • 新增APP
python manage.py startapp job
  • settings.py安装APP
INSTALLED_APPS = ['jobs',
]
  • 同步数据库
python manage.py makemigrations
python manage.py migrate

05 | 开始Django之旅 :10分钟如何创建一个可以管理职位的后台

Jobs/models.py的Job模型

created_date 和 modified_date 为自动添加的时间。

from django.db import models
from django.contrib.auth.models import User# Create your models here.
JobTypes = [(0,"技术类"),(1,"产品类"),(2,"运营类"),(3,"设计类"),(4,"市场营销类")
]Cities = [(0,"北京"),(1,"上海"),(2,"深圳"),(3,"杭州"),(4,"广州")
]class Job(models.Model):# Translators: 职位实体的翻译job_type = models.SmallIntegerField(blank=False, choices=JobTypes, verbose_name=("职位类别"))job_name = models.CharField(max_length=250, blank=False, verbose_name=("职位名称"))job_city = models.SmallIntegerField(choices=Cities, blank=False, verbose_name=("工作地点"))job_responsibility = models.TextField(max_length=1024, verbose_name=("职位职责"))job_requirement = models.TextField(max_length=1024, blank=False, verbose_name=("职位要求"))creator = models.ForeignKey(User, verbose_name=("创建人"), null=True, on_delete=models.SET_NULL)created_date = models.DateTimeField(verbose_name=("创建日期"), auto_now_add=True)modified_date = models.DateTimeField(verbose_name=("修改日期"), auto_now=True)class Meta:verbose_name = ('职位')verbose_name_plural = ('职位列表')def __str__(self):return self.job_name

06 | 产品体验优化:快速迭代完善应用

admin.py修改

save_model()使creator可以默认选择当前user。

from django.contrib import admin
from jobs.models import Jobclass JobAdmin(admin.ModelAdmin):exclude = ('creator','created_date','modified_date')list_display = ('job_name', 'job_type', 'job_city', 'creator', 'created_date', 'modified_date')def save_model(self, request, obj, form, change):if obj.creator is None:obj.creator = request.usersuper().save_model(request, obj, form, change)# Register your models here.
admin.site.register(Job,JobAdmin)

http://127.0.0.1:8000/admin/jobs/job/

07 | 添加自定义页面:让匿名用户可以浏览职位列表页

views.py

from django.shortcuts import renderfrom jobs.models import Job
from jobs.models import Cities, JobTypes# Create your views here.
def joblist(request):job_list = Job.objects.order_by('job_type')context =  {'job_list': job_list}for job in job_list:job.city_name = Cities[job.job_city][1]job.type_name = JobTypes[job.job_type][1]return render(request, 'joblist.html', context)

template/base.html

<!--jobs/templates/base.html-->
<h1 style="margin:auto;width:50%">匠果科技开放职位</h1><p></p>{% block content %}
{% endblock %}

template/joblist.html


{% extends 'base.html' %}{% block content %}{% if job_list %}<ul>{% for job in job_list %}<li>{{job.type_name}}  <a href="/job/{{ job.id }}/" style="color:blue">{{ job.job_name }}</a>   {{job.city_name}}  </li>{% endfor %}</ul>
{% else %}<p>No jobs are available.</p>
{% endif %}{% endblock %}

recruitment/urls.py

from django.contrib import admin
from django.urls import path,includeurlpatterns = [path('admin/', admin.site.urls),path("", include("jobs.urls")),
]

jobs/urls.py

from django.urls import path
from jobs import viewsurlpatterns = [# 职位列表path("joblist/", views.joblist, name="joblist"),
]

http://127.0.0.1:8000/joblist/

08 | 添加自定义页面:让匿名用户可以查看职位详情

template/job.html

{% extends 'base.html' %}{% block content %}<div style="margin:auto; width:50%;">{% if job %}<div class="position_name" z><h2>岗位名称:{{job.job_name}} </h2>城市:{{job.city_name}} <p></p></div><hr><div class="position_responsibility" style="width:600px;"><h3>岗位职责:</h3><pre style="font-size:16px">{{job.job_responsibility}}</pre></div><hr><div class="position_requirement" style="width:600px; "><h3>任职要求:</h3><pre style="font-size:16px">{{job.job_requirement}}</pre></div><div class="apply_position"><input type="button" class="btn btn-primary" style="width:120px;" value="申请" onclick="location.href='/resume/add/?apply_position={{job.job_name}}&city={{job.city_name}}'"/></div>
{% else %}<p>职位不存在</p>
{% endif %}{% endblock %}
</div>

views.py

def detail(request, job_id):try:job = Job.objects.get(pk=job_id)job.city_name = Cities[job.job_city][1]except Job.DoesNotExist:raise Http404("Job does not exist")return render(request, 'job.html', {'job': job})

更新jobs/urls.py

from django.urls import path
from jobs import viewsurlpatterns = [# 职位列表path("joblist/", views.joblist, name="joblist"),# 职位详情#url(r'^job/(?P<job_id>\d+)/$', views.detail, name='detail'),path('job/<int:job_id>/', views.detail, name='detail'),
]

09 | 开始一个正式的产品:产品背景、迭代思维与MVP产品规划

10 | 唯快不破:在产品中使用产品迭代思维

11 | 数据建模 & 企业级数据库设计原则

12 | 创建应用和模型,分组展示页面内容

两个需求可以合二为一:1)HR可以维护候选人信息;2)面试官可以维护面试反馈。

增加interview APP

python manage.py startapp interview
INSTALLED_APPS = [......'interview',......
]

jobs/models.py

# 候选人学历
DEGREE_TYPE = ((u'本科', u'本科'), (u'硕士', u'硕士'), (u'博士', u'博士'))

interview/models.py

from django.db import models
from django.contrib.auth.models import Userfrom jobs.models import DEGREE_TYPE# 第一轮面试结果
FIRST_INTERVIEW_RESULT_TYPE = ((u'建议复试', u'建议复试'), (u'待定', u'待定'), (u'放弃', u'放弃'))# 复试面试建议
INTERVIEW_RESULT_TYPE = ((u'建议录用', u'建议录用'), (u'待定', u'待定'), (u'放弃', u'放弃'))# HR终面结论
HR_SCORE_TYPE = (('S', 'S'), ('A', 'A'), ('B', 'B'), ('C', 'C'))class Candidate(models.Model):# 基础信息userid = models.IntegerField(unique=True, blank=True, null=True, verbose_name=u'应聘者ID')username = models.CharField(max_length=135, verbose_name=u'姓名')city = models.CharField(max_length=135, verbose_name=u'城市')phone = models.CharField(max_length=135, verbose_name=u'手机号码')email = models.EmailField(max_length=135, blank=True, verbose_name=u'邮箱')apply_position = models.CharField(max_length=135, blank=True, verbose_name=u'应聘职位')born_address = models.CharField(max_length=135, blank=True, verbose_name=u'生源地')gender = models.CharField(max_length=135, blank=True, verbose_name=u'性别')candidate_remark = models.CharField(max_length=135, blank=True, verbose_name=u'候选人信息备注')# 学校与学历信息bachelor_school = models.CharField(max_length=135, blank=True, verbose_name=u'本科学校')master_school = models.CharField(max_length=135, blank=True, verbose_name=u'研究生学校')doctor_school = models.CharField(max_length=135, blank=True, verbose_name=u'博士生学校')major = models.CharField(max_length=135, blank=True, verbose_name=u'专业')degree = models.CharField(max_length=135, choices=DEGREE_TYPE, blank=True, verbose_name=u'学历')# 综合能力测评成绩,笔试测评成绩test_score_of_general_ability = models.DecimalField(decimal_places=1, null=True, max_digits=3, blank=True,verbose_name=u'综合能力测评成绩')paper_score = models.DecimalField(decimal_places=1, null=True, max_digits=3, blank=True, verbose_name=u'笔试成绩')# 第一轮面试记录first_score = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True, verbose_name=u'初试分',help_text=u'1-5分,极优秀: >=4.5,优秀: 4-4.4,良好: 3.5-3.9,一般: 3-3.4,较差: <3分')first_learning_ability = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'学习能力得分')first_professional_competency = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'专业能力得分')first_advantage = models.TextField(max_length=1024, blank=True, verbose_name=u'优势')first_disadvantage = models.TextField(max_length=1024, blank=True, verbose_name=u'顾虑和不足')first_result = models.CharField(max_length=256, choices=FIRST_INTERVIEW_RESULT_TYPE, blank=True,verbose_name=u'初试结果')first_recommend_position = models.CharField(max_length=256, blank=True, verbose_name=u'推荐部门')first_interviewer_user = models.ForeignKey(User, related_name='first_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'面试官')first_remark = models.CharField(max_length=135, blank=True, verbose_name=u'初试备注')# 第二轮面试记录second_score = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True, verbose_name=u'专业复试得分',help_text=u'1-5分,极优秀: >=4.5,优秀: 4-4.4,良好: 3.5-3.9,一般: 3-3.4,较差: <3分')second_learning_ability = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'学习能力得分')second_professional_competency = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'专业能力得分')second_pursue_of_excellence = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'追求卓越得分')second_communication_ability = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'沟通能力得分')second_pressure_score = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True,verbose_name=u'抗压能力得分')second_advantage = models.TextField(max_length=1024, blank=True, verbose_name=u'优势')second_disadvantage = models.TextField(max_length=1024, blank=True, verbose_name=u'顾虑和不足')second_result = models.CharField(max_length=256, choices=INTERVIEW_RESULT_TYPE, blank=True, verbose_name=u'专业复试结果')second_recommend_position = models.CharField(max_length=256, blank=True, verbose_name=u'建议方向或推荐部门')second_interviewer_user = models.ForeignKey(User, related_name='second_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'二面面试官')second_remark = models.CharField(max_length=135, blank=True, verbose_name=u'专业复试备注')# HR终面hr_score = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True, verbose_name=u'HR复试综合等级')hr_responsibility = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True, verbose_name=u'HR责任心')hr_communication_ability = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True,verbose_name=u'HR坦诚沟通')hr_logic_ability = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True, verbose_name=u'HR逻辑思维')hr_potential = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True, verbose_name=u'HR发展潜力')hr_stability = models.CharField(max_length=10, choices=HR_SCORE_TYPE, blank=True, verbose_name=u'HR稳定性')hr_advantage = models.TextField(max_length=1024, blank=True, verbose_name=u'优势')hr_disadvantage = models.TextField(max_length=1024, blank=True, verbose_name=u'顾虑和不足')hr_result = models.CharField(max_length=256, choices=INTERVIEW_RESULT_TYPE, blank=True, verbose_name=u'HR复试结果')hr_interviewer_user = models.ForeignKey(User, related_name='hr_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'HR面试官')hr_remark = models.CharField(max_length=256, blank=True, verbose_name=u'HR复试备注')creator = models.CharField(max_length=256, blank=True, verbose_name=u'候选人数据的创建人')created_date = models.DateTimeField(auto_now_add=True, verbose_name=u'创建时间')modified_date = models.DateTimeField(auto_now=True, null=True, blank=True, verbose_name=u'更新时间')last_editor = models.CharField(max_length=256, blank=True, verbose_name=u'最后编辑者')class Meta:db_table = u'candidate'verbose_name = u'应聘者'verbose_name_plural = u'应聘者'# Python 3 直接定义 __str__() 方法即可,系统使用这个方法来把对象转换成字符串def __str__(self):return self.username

interview/admin.py

fieldsets的快速设定方法:填写fieldsets时,可把models的字段全部拷贝进去再删除。

from django.contrib import admin
from interview.models import Candidate# Register your models here.# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):exclude = ('creator', 'created_date', 'modified_date')list_display = ('username', 'city', 'bachelor_school', 'first_score', 'first_result', 'first_interviewer_user', 'second_score','second_result', 'second_interviewer_user', 'hr_score', 'hr_result', 'hr_interviewer_user',)# 分组展示字段,分三块,基础信息、第一轮面试记录、第二轮面试(专业复试)、HR复试fieldsets = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),('HR复试', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability"), ("hr_potential", "hr_stability"),"hr_advantage", "hr_disadvantage", "hr_result", "hr_interviewer_user", "hr_remark",)}),)admin.site.register(Candidate, CandidateAdmin)

http://127.0.0.1:8000/admin/interview/candidate/

http://127.0.0.1:8000/admin/interview/candidate/1/change/

13 | 产品新需求:如何批量从Excel文件导入候选人数据(命令行工具)

参考django官方文档链接

新增interview\management\commands\import_candidates.py

可以使用python manage.py import_candidates --path /path/to/your/file.csv来批量导入信息。

import csv
from django.core.management import BaseCommand
from interview.models import Candidate# run command to import candidates:
# python manage.py import_candidates --path /path/to/your/file.csvclass Command(BaseCommand):help = '从一个CSV文件的内容中读取候选人列表,导入到数据库中'def add_arguments(self, parser):parser.add_argument('--path', type=str)def handle(self, *args, **kwargs):path = kwargs['path']with open(path, 'rt', encoding="gbk") as f:reader = csv.reader(f, dialect='excel', delimiter=';')for row in reader:candidate = Candidate.objects.create(username=row[0],city=row[1],phone=row[2],bachelor_school=row[3],major=row[4],degree=row[5],test_score_of_general_ability=row[6],paper_score=row[7])print(candidate)

命令行

C:\Users\Season\Desktop\4.python网页前后端\Django基础教程\workspace4-recruiting\recruitment【13-】>python manage.py import_candidates --path candidates.csv
刘倩
陈欣欣
刘德华
李小龙

http://127.0.0.1:8000/admin/interview/candidate/

admin管理界面可以看到批量导入的名单。

14 | 产品体验优化:候选人列表筛选和查询

更新interview/admin.py

新增以下代码

    # 右侧筛选条件list_filter = ('city','first_result','second_result','hr_result','first_interviewer_user','second_interviewer_user','hr_interviewer_user')# 查询字段search_fields = ('username', 'phone', 'email', 'bachelor_school')### 列表页排序字段ordering = ('hr_result','second_result','first_result',)

变更后的admin.py

from django.contrib import admin
from interview.models import Candidate# Register your models here.# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):exclude = ('creator', 'created_date', 'modified_date')list_display = ('username', 'city', 'bachelor_school', 'first_score', 'first_result', 'first_interviewer_user', 'second_score','second_result', 'second_interviewer_user', 'hr_score', 'hr_result', 'hr_interviewer_user',)# 右侧筛选条件list_filter = ('city','first_result','second_result','hr_result','first_interviewer_user','second_interviewer_user','hr_interviewer_user')# 查询字段search_fields = ('username', 'phone', 'email', 'bachelor_school')### 列表页排序字段ordering = ('hr_result','second_result','first_result',)# 分组展示字段,分三块,基础信息、第一轮面试记录、第二轮面试(专业复试)、HR复试fieldsets = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),('HR复试', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability"), ("hr_potential", "hr_stability"),"hr_advantage", "hr_disadvantage", "hr_result", "hr_interviewer_user", "hr_remark",)}),)admin.site.register(Candidate, CandidateAdmin)#admin.site.register(Candidate)

http://127.0.0.1:8000/admin/interview/candidate/?o=11.8.-5

15 | 省去单独的账号管理工作:企业域账号集成(略)

16 | 批量设置面试官:面试官的导入、授权

建立hr和interview的权限群组



将user赋予权限群组

17 | 产品新需求 :如何导出候选人的数据到CSV(增加自定义的数据操作菜单)

django链接
其他参考链接添加链接描述
response.charset = ‘utf-8-sig’ # 可选, 修改编码为带BOM的utf-8格式(Excel打开不会有乱码)

interview/admin.py

新增export_model_as_csv()、exportable_fields ,将actions = (export_model_as_csv, )加进去class CandidateAdmin。

from django.contrib import admin
from django.http import HttpResponse
from interview.models import Candidate
from datetime import datetime
import csv
# Register your models here.exportable_fields = ('username', 'city', 'phone', 'bachelor_school', 'master_school', 'degree', 'first_result', 'first_interviewer_user','second_result', 'second_interviewer_user', 'hr_result', 'hr_score', 'hr_remark', 'hr_interviewer_user')# define export action
def export_model_as_csv(modeladmin, request, queryset):response = HttpResponse(content_type='text/csv')field_list = exportable_fieldsresponse['Content-Disposition'] = 'attachment; filename=%s-list-%s.csv' % ('recruitment-candidates',datetime.now().strftime('%Y-%m-%d-%H-%M-%S'),)# 写入表头response.charset = 'utf-8-sig'  # 可选, 修改编码为带BOM的utf-8格式(Excel打开不会有乱码)writer = csv.writer(response)writer.writerow([queryset.model._meta.get_field(f).verbose_name.title() for f in field_list],)for obj in queryset:## 单行 的记录(各个字段的值), 根据字段对象,从当前实例 (obj) 中获取字段值csv_line_values = []for field in field_list:field_object = queryset.model._meta.get_field(field)field_value = field_object.value_from_object(obj)csv_line_values.append(field_value)writer.writerow(csv_line_values)return responseexport_model_as_csv.short_description = u'导出为CSV文件'# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):exclude = ('creator', 'created_date', 'modified_date')actions = (export_model_as_csv,  )list_display = ('username', 'city', 'bachelor_school', 'first_score', 'first_result', 'first_interviewer_user', 'second_score','second_result', 'second_interviewer_user', 'hr_score', 'hr_result', 'hr_interviewer_user',)# 右侧筛选条件list_filter = ('city','first_result','second_result','hr_result','first_interviewer_user','second_interviewer_user','hr_interviewer_user')# 查询字段search_fields = ('username', 'phone', 'email', 'bachelor_school')### 列表页排序字段ordering = ('hr_result','second_result','first_result',)# 分组展示字段,分三块,基础信息、第一轮面试记录、第二轮面试(专业复试)、HR复试fieldsets = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),('HR复试', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability"), ("hr_potential", "hr_stability"),"hr_advantage", "hr_disadvantage", "hr_result", "hr_interviewer_user", "hr_remark",)}),)admin.site.register(Candidate, CandidateAdmin)#admin.site.register(Candidate)

http://127.0.0.1:8000/admin/interview/candidate/


18 | 如何记录日志方便排查问题

django的logging文档

https://docs.djangoproject.com/zh-hans/3.1/topics/logging/#topic-logging-parts-handlers

设置LOGGING:settings.py(简单版)

当logger中的django_python3_ldap的level在DEBUG以上,就调用handlers–“console”。而handlers已经定义了指定的输出方式。而formatter是指定输出格式。


# 日志
LOGGING = {'version': 1,'disable_existing_loggers': False,'handlers': {'console': {'class': 'logging.StreamHandler',},},'loggers': {"django_python3_ldap": {"handlers": ["console"],"level": "DEBUG",},},
}

设置LOGGING:settings.py(进阶版)


LOGGING = {'version': 1,'disable_existing_loggers': False,'formatters': {'simple': { # exact format is not important, this is the minimum information'format': '%(asctime)s %(name)-12s %(lineno)d %(levelname)-8s %(message)s',},},'handlers': {'console': {'class': 'logging.StreamHandler','formatter': 'simple',},'mail_admins': { # Add Handler for mail_admins for `warning` and above'level': 'ERROR','class': 'django.utils.log.AdminEmailHandler',},'file': {#'level': 'INFO','class': 'logging.FileHandler','formatter': 'simple','filename': os.path.join(LOG_DIR, 'recruitment.admin.log'),},'performance': {#'level': 'INFO','class': 'logging.FileHandler','formatter': 'simple','filename': os.path.join(LOG_DIR, 'recruitment.performance.log'),},},'root': {'handlers': ['console', 'file'],'level': 'INFO',},'loggers': {"django_python3_ldap": {"handlers": ["console", "file"],"level": "DEBUG",},"interview.performance": {"handlers": ["console", "performance"],"level": "INFO","propagate": False,},'django': {"handlers": ["console","file"],"level": "INFO",'propagate': True,},},
}

设置LOGGING:settings.py(其实可以写少一点)

LOGGING = {'version':1,'disable_existing_loggers':False,'formatters':{'simple':{'format':'%(asctime)s %(name)-12s %(lineno)d %(levelname)-8s %(message)s',}},'handlers':{'console':{'class':'logging.StreamHandler','formatter':'simple',},'file':{'class':'logging.FileHandler','formatter':'simple','filename':os.path.join(LOG_DIR,'recruitment.admin.log')},},'root':{'handlers':['console','file'],'level':'INFO',},
}

在模块中使用log:interview/admin.py

备注:这里默认会调用root。不会使用loggers里面的如interview.performance。
注意logger = logging.getLogger( name )和logger.error(" %s has exported %s candidate records" % (request.user.username, len(queryset)))

from django.contrib import admin
from django.http import HttpResponse
from interview.models import Candidate
from datetime import datetime
import csvimport logging
logger = logging.getLogger(__name__)exportable_fields = ('username', 'city', 'phone', 'bachelor_school', 'master_school', 'degree', 'first_result', 'first_interviewer_user','second_result', 'second_interviewer_user', 'hr_result', 'hr_score', 'hr_remark', 'hr_interviewer_user')# define export action
def export_model_as_csv(modeladmin, request, queryset):response = HttpResponse(content_type='text/csv')field_list = exportable_fieldsresponse['Content-Disposition'] = 'attachment; filename=%s-list-%s.csv' % ('recruitment-candidates',datetime.now().strftime('%Y-%m-%d-%H-%M-%S'),)# 写入表头writer = csv.writer(response)writer.writerow([queryset.model._meta.get_field(f).verbose_name.title() for f in field_list],)for obj in queryset:## 单行 的记录(各个字段的值), 根据字段对象,从当前实例 (obj) 中获取字段值csv_line_values = []for field in field_list:field_object = queryset.model._meta.get_field(field)field_value = field_object.value_from_object(obj)csv_line_values.append(field_value)writer.writerow(csv_line_values)logger.error(" %s has exported %s candidate records" % (request.user.username, len(queryset)))return responseexport_model_as_csv.short_description = u'导出为CSV文件'

recruitment.admin.log

2021-11-16 07:30:11,714 django.utils.autoreload 613 INFO     Watching for file changes with StatReloader
2021-11-16 07:30:11,714 django.utils.autoreload 613 INFO     Watching for file changes with StatReloader
2021-11-16 07:30:58,594 django.server 157 INFO     "GET /admin/interview/candidate/ HTTP/1.1" 200 15726
2021-11-16 07:30:58,594 django.server 157 INFO     "GET /admin/interview/candidate/ HTTP/1.1" 200 15726
2021-11-16 07:31:03,627 interview.admin 38 ERROR     season has exported 5 candidate records
2021-11-16 07:31:03,629 django.server 157 INFO     "POST /admin/interview/candidate/ HTTP/1.1" 200 496
2021-11-16 07:31:03,629 django.server 157 INFO     "POST /admin/interview/candidate/ HTTP/1.1" 200 496

LogRecord attributes

链接


19 | 生产环境的配置如何管理更安全: 生产环境与开发环境配置分离

简单来说,就是base是基类,然后python manage runserver时候切换local或production环境的设定,如果字典没有设定值,就采用base基类的默认值。

manage.py

os.environ.setdefault(‘DJANGO_SETTINGS_MODULE’, ‘settings.base’)

#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sysdef main():"""Run administrative tasks."""#os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'recruitment.settings')os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.base')try:from django.core.management import execute_from_command_lineexcept ImportError as exc:raise ImportError("Couldn't import Django. Are you sure it's installed and ""available on your PYTHONPATH environment variable? Did you ""forget to activate a virtual environment?") from excexecute_from_command_line(sys.argv)if __name__ == '__main__':main()

将settings.py变为settings文件夹

local.py

from .base import *DEBUG = TrueALLOWED_HOSTS = ["recruit.ihopeit.com","127.0.0.1"]

production.py

其中,DEBUG可以设定为False。

from .base import *DEBUG = FalseALLOWED_HOSTS = ["recruit.ihopeit.com","127.0.0.1"]

runserver - production.bat来启动生产环境

python manage.py runserver --settings=settings.production

runserver - local.bat来启动本地环境

python manage.py runserver --settings=settings.local

settings/base.py

"""
Django settings for recruitment project.Generated by 'django-admin startproject' using Django 3.2.7.For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""from pathlib import Path
import os# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
LOG_DIR = os.path.dirname(BASE_DIR)# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-s=_#6o=h)t_2*c^sor=1gtmrpa(5&oie+k_ilxh$7#&&^9z$l6'# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = TrueALLOWED_HOSTS = []# Application definitionINSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','jobs','interview',
]MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',
]ROOT_URLCONF = 'recruitment.urls'TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [],'APP_DIRS': True,'OPTIONS': {'context_processors': ['django.template.context_processors.debug','django.template.context_processors.request','django.contrib.auth.context_processors.auth','django.contrib.messages.context_processors.messages',],},},
]WSGI_APPLICATION = 'recruitment.wsgi.application'# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databasesDATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3','NAME': BASE_DIR / 'db.sqlite3',}
}# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validatorsAUTH_PASSWORD_VALIDATORS = [{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',},{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',},{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',},{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',},
]# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/LANGUAGE_CODE = 'zh-hans'TIME_ZONE = 'Asia/Shanghai'USE_I18N = TrueUSE_L10N = TrueUSE_TZ = True# 日志
# LOGGING = {#     'version': 1,
#     'disable_existing_loggers': False,
#     'handlers': {#         'console': {#             'class': 'logging.StreamHandler',
#         },
#     },
#     'loggers': {#         "django_python3_ldap": {#             "handlers": ["console"],
#             "level": "DEBUG",
#         },
#     },
# }LOGGING = {'version': 1,'disable_existing_loggers': False,'formatters': {'simple': { # exact format is not important, this is the minimum information'format': '%(asctime)s %(name)-12s %(lineno)d %(levelname)-8s %(message)s',},},'handlers': {'console': {'class': 'logging.StreamHandler','formatter': 'simple',},'mail_admins': { # Add Handler for mail_admins for `warning` and above'level': 'ERROR','class': 'django.utils.log.AdminEmailHandler',},'file': {#'level': 'INFO','class': 'logging.FileHandler','formatter': 'simple','filename': os.path.join(LOG_DIR, 'recruitment.admin.log'),},'performance': {#'level': 'INFO','class': 'logging.FileHandler','formatter': 'simple','filename': os.path.join(LOG_DIR, 'recruitment.performance.log'),},},'root': {'handlers': ['console', 'file'],'level': 'INFO',},'loggers': {"django_python3_ldap": {"handlers": ["console", "file"],"level": "DEBUG",},"interview.performance": {"handlers": ["console", "performance"],"level": "INFO","propagate": False,},'django': {"handlers": ["console","file"],"level": "INFO",'propagate': True,},},
}# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/STATIC_URL = '/static/'# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-fieldDEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

20 | 产品细节完善

需求点说明

修改站点命名

  • recruitement/urls.py
admin.site.site_header = _('招聘管理系统')
  • 实际效果

设定字段提示help_text

  • interview/models.py
    first_score = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True, verbose_name=u'初试分',help_text=u'1-5分,极优秀: >=4.5,优秀: 4-4.4,良好: 3.5-3.9,一般: 3-3.4,较差: <3分')second_score = models.DecimalField(decimal_places=1, null=True, max_digits=2, blank=True, verbose_name=u'专业复试得分', help_text=u'1-5分,极优秀: >=4.5,优秀: 4-4.4,良好: 3.5-3.9,一般: 3-3.4,较差: <3分')
  • 实际效果

面试官信息与登录用户关联,面试官只有HR可以修改

  • interview/models.py
    first_interviewer_user = models.ForeignKey(User, related_name='first_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'一面面试官')second_interviewer_user = models.ForeignKey(User, related_name='second_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'二面面试官')hr_interviewer_user = models.ForeignKey(User, related_name='hr_interviewer_user', blank=True, null=True, on_delete=models.CASCADE, verbose_name=u'HR面试官')
  • 实际效果

对于面试官角色,设置一面面试官字段、二面面试官字段为只读属性(运用get_readonly_fields方法)

  • interview/admin.py
# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):### 设定只读字段#readonly_fields = ('first_interviewer_user','second_interviewer_user',)def get_group_names(self, user):group_names = []for g in user.groups.all():group_names.append(g.name)return group_namesdef get_readonly_fields(self, request, obj):group_names = self.get_group_names(request.user)if 'interviewer' in group_names:logger.info("interviewer is in user's group for %s" % request.user.username)return ('first_interviewer_user','second_interviewer_user',)return ()
  • 实际效果

HR角色可以在列表页面直接修改一面面试官、二面面试官:list_editable属性

  • interview/admin.py
    ### HR在列表页直接编辑面试官信息default_list_editable = ('first_interviewer_user','second_interviewer_user',)def get_list_editable(self, request):group_names = self.get_group_names(request.user)if request.user.is_superuser or 'hr' in group_names:return self.default_list_editablereturn ()def get_changelist_instance(self, request):self.list_editable = self.get_list_editable(request)return super().get_changelist_instance(request)
  • HR可编辑面试官字段
  • 面试官只能查看面试官字段
  • 被重写的方法(位于option.py)
    def get_readonly_fields(self, request, obj=None):"""Hook for specifying custom readonly fields."""return self.readonly_fieldsdef get_changelist_instance(self, request):"""Return a `ChangeList` instance based on `request`. May raise`IncorrectLookupParameters`."""list_display = self.get_list_display(request)list_display_links = self.get_list_display_links(request, list_display)# Add the action checkboxes if any actions are available.if self.get_actions(request):list_display = ['action_checkbox', *list_display]sortable_by = self.get_sortable_by(request)ChangeList = self.get_changelist(request)return ChangeList(request,self.model,list_display,list_display_links,self.get_list_filter(request),self.date_hierarchy,self.get_search_fields(request),self.get_list_select_related(request),self.list_per_page,self.list_max_show_all,self.list_editable,self,sortable_by,)

21 | 定制更美观的主题

安装步骤

CMD

C:\Users\Season>pip install -i https://pypi.tuna.tsinghua.edu.cn/simple  django-grappelli

settings/base.py

# Application definitionINSTALLED_APPS = ['grappelli','django.contrib.admin',
......
]

urls.py

    path('grappelli/', include("grappelli.urls")),path('admin/', admin.site.urls),

实际效果较django原生页面要好看

22 | 定制面试官权限

需求描述

数据权限:专业面试官只能评估自己负责的环节1

重写get_fieldsets()方法。

  • interview/admin.py
# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):# 分组展示字段,分三块,基础信息、第一轮面试记录、第二轮面试(专业复试)、HR复试default_fieldsets = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),('HR复试', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability"), ("hr_potential", "hr_stability"),"hr_advantage", "hr_disadvantage", "hr_result", "hr_interviewer_user", "hr_remark",)}),)# 一面面试官字段default_fieldsets_first = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),)# 二面面试官字段default_fieldsets_second = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),)# 一面面试官只可以修改一面信息,二面面试官只可以修改二面信息def get_fieldsets(self, request, obj):group_names = self.get_group_names(request.user)if 'interviewer' in group_names and obj.first_interviewer_user == request.user:return self.default_fieldsets_firstif 'interviewer' in group_names and obj.second_interviewer_user == request.user:return self.default_fieldsets_secondreturn self.default_fieldsets

数据权限:专业面试官只能评估自己负责的环节2–优化candidate_fieldsets

  • interview/admin.py
import interview.candidate_fieldsets as cf# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):# 一面面试官只可以修改一面信息,二面面试官只可以修改二面信息def get_fieldsets(self, request, obj):group_names = self.get_group_names(request.user)if 'interviewer' in group_names and obj.first_interviewer_user == request.user:#return self.default_fieldsets_firstreturn cf.default_fieldsets_firstif 'interviewer' in group_names and obj.second_interviewer_user == request.user:#return self.default_fieldsets_secondreturn cf.default_fieldsets_second#return self.default_fieldsetsreturn cf.default_fieldsets
  • interview/candidate_fieldsets.py
# 分组展示字段,分三块,基础信息、第一轮面试记录、第二轮面试(专业复试)、HR复试
default_fieldsets = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),('HR复试', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability"), ("hr_potential", "hr_stability"),"hr_advantage", "hr_disadvantage", "hr_result", "hr_interviewer_user", "hr_remark",)}),)# 一面面试官字段
default_fieldsets_first = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第一轮面试', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage","first_result", "first_recommend_position", "first_interviewer_user", "first_remark",)}),)# 二面面试官字段
default_fieldsets_second = ((None, {'fields': ("userid", ("username", "city", "phone"), ("email", "apply_position", "born_address", "gender", "candidate_remark"),("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), "test_score_of_general_ability","paper_score",)}),('第二轮面试(专业复试)', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency"), ("second_pursue_of_excellence", "second_communication_ability", "second_pressure_score"), "second_advantage","second_disadvantage", "second_result", "second_recommend_position","second_interviewer_user", "second_remark",)}),)

数据集权限:专业面试官只能看到分到自己的候选人

重写get_queryset()方法

  • interview/admin.py
from django.db.models import Q# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):# 对于非管理员,非HR,获取自己是一面面试官或者二面面试官的候选人集合:sdef get_queryset(self, request):  # show data only owned by the userqs = super(CandidateAdmin, self).get_queryset(request)group_names = self.get_group_names(request.user)if request.user.is_superuser or 'hr' in group_names:return qsreturn Candidate.objects.filter(Q(first_interviewer_user=request.user) | Q(second_interviewer_user=request.user))

功能权限:数据导出权限仅HR和超级管理员可用

  • interview/models.py 新增导出权限
class Candidate(models.Model):class Meta:db_table = u'candidate'verbose_name = u'应聘者'verbose_name_plural = u'应聘者'#增加候选人的权限permissions = [("export", "Can export candidate list"),("notify", "notify interviewer for candidate review"),]
  • interview/admin.py 检查用户是否有导出权限(导出权限是admin管理界面中设定的。)
# 增加导出权限管控
export_model_as_csv.allowed_permissions = ('export',)# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):# 当前用户是否有导出权限:def has_export_permission(self, request):opts = self.optsreturn request.user.has_perm('%s.%s' % (opts.app_label, "export"))

23 | 系统报错功能:钉钉群消息集成&通知一面面试官

钉钉通知步骤

注册钉钉机器人


interview/dingtalk.py

#coding=utf-8
from dingtalkchatbot.chatbot import DingtalkChatbotfrom django.conf import settingsdef send(message, at_mobiles=[]):# 引用 settings里面配置的钉钉群消息通知的WebHook地址:webhook = settings.DINGTALK_WEB_HOOK# 初始化机器人小丁, # 方式一:通常初始化方式xiaoding = DingtalkChatbot(webhook)# 方式二:勾选“加签”选项时使用(v1.5以上新功能)# xiaoding = DingtalkChatbot(webhook, secret=secret)# Text消息@所有人xiaoding.send_text(msg=('面试通知: %s' % message), at_mobiles = at_mobiles )

settings/local.py

from .base import *DEBUG = TrueALLOWED_HOSTS = ["recruit.ihopeit.com","127.0.0.1"]## 务必修改以下值,确保运行时系统安全:
SECRET_KEY = "w$46bks+b3-7f(13#i%v@jwejrnxc$^^#@#@^t@fofizy1^mo9r8(-939243423300"## 如果仅使用数据库中的账号,以下 LDAP 配置可忽略
## 替换这里的配置为正确的域服务器配置,同时可能需要修改 base.py 中的 LDAP 服务器相关配置:
LDAP_AUTH_URL = "ldap://xxxxx:389"
LDAP_AUTH_CONNECTION_USERNAME = "admin"
LDAP_AUTH_CONNECTION_PASSWORD = "your_admin_credentials"INSTALLED_APPS += (# other apps for production site
)## 钉钉群的 WEB_HOOK, 用于发送钉钉消息
DINGTALK_WEB_HOOK = "https://oapi.dingtalk.com/robot/send?access_token=***********************"

shell来启动发消息程序

C:\Users\Season>cd C:\Users\Season\Desktop\4.python网页前后端\Django基础教程\workspace4-recruiting\recruitment【23】C:\Users\Season\Desktop\4.python网页前后端\Django基础教程\workspace4-recruiting\recruitment【23】>python manage.py shell --settings=settings.local
Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)]
Type 'copyright', 'credits' or 'license' for more information
IPython 7.14.0 -- An enhanced Interactive Python. Type '?' for help.In [1]: from interview import dingtalkIn [2]: dingtalk.send("通知12314134")In [3]:

通知一面面试官面试

  • interview/admin.py
# 通知一面面试官面试
def notify_interviewer(modeladmin, request, queryset):candidates = ""interviewers = ""for obj in queryset:candidates = obj.username + ";" + candidatesinterviewers = obj.first_interviewer_user.username + ";" + interviewers# 这里的消息发送到钉钉, 或者通过 Celery 异步发送到钉钉dingtalk.send ("候选人 %s 进入面试环节,亲爱的面试官,请准备好面试: %s" % (candidates, interviewers) )notify_interviewer.short_description = u'通知一面面试官'# 候选人管理类
class CandidateAdmin(admin.ModelAdmin):actions = (export_model_as_csv, notify_interviewer, )


24 | 允许候选人注册登录: 集成Registration

标准步骤:

其中需要使用pip直接安装最新的django-registration-redux-2.9。如果使用清华镜像会失败pip install -i https://pypi.tuna.tsinghua.edu.cn/simple django-registration-redux。

1、安装django-registration-redux

- 安装应用
pip install django-registration-redux- 在settings.base 中install app
'registration', # 用户登陆注册功能- 有新的库表数据,需要迁移
python manage.py makemigrations/ python manage.py migrate

2、用户注册

项目url设置登陆url: path('accounts/', include('registration.backends.simple.urls')),

先到django后台将用户登出,然后访问:/accounts/register/来注册用户

3、用户登陆

accounts/login/

输入用户名密码登陆

4、注册跳转、登陆跳转

  • 4-1、settings.base 中设置跳转URL:
# 登陆后跳转首页
LOGIN_REDIRECT_URL = '/'# 注册后跳转登陆
SIMPLE_BACKEND_REDIRECT_URL = '/accounts/login/'
  • 4-2、设置首页是招聘列表joblist
项目url(recrument.urls): url(r"^", include("jobs.urls")),
应用url(jobs.urls): path("", views.joblist, name="name"),

曾经的错误

  • 迁移数据库时会发生错误。AttributeError: module ‘django.contrib.auth.views’ has no attribute ‘login’==>解决措施为安装django-registration-redux-2.9。

25 | 候选人简历存储:创建简历Model

jobs/models.py新增Resume模型

class Resume(models.Model):# Translators: 简历实体的翻译username = models.CharField(max_length=135, verbose_name=_('姓名'))applicant = models.ForeignKey(User, verbose_name=_("申请人"), null=True, on_delete=models.SET_NULL)city = models.CharField(max_length=135, verbose_name=_('城市'))phone = models.CharField(max_length=135,  verbose_name=_('手机号码'))email = models.EmailField(max_length=135, blank=True, verbose_name=_('邮箱'))apply_position = models.CharField(max_length=135, blank=True, verbose_name=_('应聘职位'))born_address = models.CharField(max_length=135, blank=True, verbose_name=_('生源地'))gender = models.CharField(max_length=135, blank=True, verbose_name=_('性别'))picture = models.ImageField(upload_to='images/', blank=True, verbose_name=_('个人照片')) attachment = models.FileField(upload_to='file/', blank=True, verbose_name=_('简历附件'))# 学校与学历信息bachelor_school = models.CharField(max_length=135, blank=True, verbose_name=_('本科学校'))master_school = models.CharField(max_length=135, blank=True, verbose_name=_('研究生学校'))doctor_school = models.CharField(max_length=135, blank=True, verbose_name=u'博士生学校')major = models.CharField(max_length=135, blank=True, verbose_name=_('专业'))degree = models.CharField(max_length=135, choices=DEGREE_TYPE, blank=True, verbose_name=_('学历'))created_date = models.DateTimeField(verbose_name="创建日期", default=datetime.now)modified_date = models.DateTimeField(verbose_name="修改日期", auto_now=True)# 候选人自我介绍,工作经历,项目经历candidate_introduction = models.TextField(max_length=1024, blank=True, verbose_name=u'自我介绍')work_experience = models.TextField(max_length=1024, blank=True, verbose_name=u'工作经历')project_experience = models.TextField(max_length=1024, blank=True, verbose_name=u'项目经历')class Meta:verbose_name = _('简历')verbose_name_plural = _('简历列表')def __str__(self):return self.username

jobs/admin.py定义RusumeAdmin


class ResumeAdmin(admin.ModelAdmin):list_display = ('username', 'applicant', 'city', 'apply_position', 'bachelor_school', 'master_school', 'major','created_date')readonly_fields = ('applicant', 'created_date', 'modified_date',)fieldsets = ((None, {'fields': ("applicant", ("username", "city", "phone"),("email", "apply_position", "born_address", "gender", ), ("picture", "attachment",),("bachelor_school", "master_school"), ("major", "degree"), ('created_date', 'modified_date'),"candidate_introduction", "work_experience","project_experience",)}),)def save_model(self, request, obj, form, change):obj.applicant = request.usersuper().save_model(request, obj, form, change)# Register your models here.
admin.site.register(Job,JobAdmin)
admin.site.register(Resume, ResumeAdmin)

jobs/template/base.html更新

<!--jobs/templates/base.html-->
<h1 style="margin:auto;width:50%">匠果科技开放职位</h1><p></p>{% block header %}
<a href="/" style="text-decoration: none; color:#007bff">Homepage</a>
<a href="/joblist" style="text-decoration: none; color:#007bff">job_list</a>
{% if user.is_authenticated %}<a href="/accounts/logout" style="text-decoration: none; color:#007bff">Logout</a>
{% else %}<a href="/accounts/login" style="text-decoration: none; color:#007bff">Login</a>
{% endif %}
{% if user.is_authenticated %}<p>终于等到你 {{ user_name }}, 期待加入我们,用技术去探索一个新世界</p>
{% else %}<br>"欢迎你,期待加入我们,登陆后可以提交简历."<br>
{% endif %}
{% endblock %}{% block content %}
{% endblock %}

26 | 让候选人可以在线投递简历

目标和步骤

jobs/template/resume_form.html

<h2>提交简历</h2>
<form method="post" style="width:600px;margin-left:5px">{% csrf_token %}{{ form.as_p }}<input type="submit" value="提交"></form>

jobs/views.py


class ResumeCreateView(LoginRequiredMixin, CreateView):"""    简历职位页面  """template_name = 'resume_form.html'success_url = '/joblist/'model = Resumefields = ["username", "city", "phone","email", "apply_position", "gender","bachelor_school", "master_school", "major", "degree", "picture", "attachment","candidate_introduction", "work_experience", "project_experience"]## 从 URL 请求参数带入默认值def get_initial(self):initial = {}for x in self.request.GET:initial[x] = self.request.GET[x]return initialdef form_valid(self, form):self.object = form.save(commit=False)self.object.applicant = self.request.userself.object.save()return HttpResponseRedirect(self.get_success_url())

jobs/urls.py

    # 提交简历path('resume/add/',views.ResumeCreateView.as_view(),name='resume-add'),

27 | 使用Bootstrap来定制页面样式

流程说明

修改base.py来添加到apps

INSTALLED_APPS = ['grappelli','bootstrap4','registration','django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','jobs','interview',
]

模板使用bootstrap标签

  • base.html
<!--bootstrap start-->
{# Load the tag library #}
{% load bootstrap4 %}{% load i18n %}{# Load CSS and JavaScript #}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}{# Display django.contrib.messages as Bootstrap alerts #}
{% bootstrap_messages %}
<!--bootstrap end-->
  • resume_form.html
<!--bootstrap start-->
{# Load the tag library #}
{% load bootstrap4 %}{% load i18n %}{# Load CSS and JavaScript #}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}{# Display django.contrib.messages as Bootstrap alerts #}
{% bootstrap_messages %}
<!--bootstrap end-->

效果对比

没有bootstrap

有bootstrap

支持手动微调整

        <input type="button" class="btn btn-danger" style="width:120px;" value="申请" onclick="location.href='/resume/add/?apply_position={{job.job_name}}&city={{job.city_name}}'"/>

28 | 简历评估&安排一面面试官

流程说明

jobs\admin.py拷贝Resume对象中的属性到Candidate

from django.contrib import admin
from jobs.models import Job,Resume
from django.contrib import messages
from interview.models import Candidate
from datetime import datetimedef enter_interview_process(modeladmin, request, queryset):candidate_names = ""for resume in queryset:candidate = Candidate()# 把 obj 对象中的所有属性拷贝到 candidate 对象中:candidate.__dict__.update(resume.__dict__)candidate.created_date = datetime.now()candidate.modified_date = datetime.now()candidate_names = candidate.username + "," + candidate_namescandidate.creator = request.user.usernamecandidate.save()messages.add_message(request, messages.INFO, '候选人: %s 已成功进入面试流程' % (candidate_names) )enter_interview_process.short_description = u"进入面试流程"

jobs\admin.py注册到modeladmin中

添加actions = (enter_interview_process,)

class ResumeAdmin(admin.ModelAdmin):actions = (enter_interview_process,)list_display = ('username', 'applicant', 'city', 'apply_position', 'bachelor_school', 'master_school', 'major','created_date')readonly_fields = ('applicant', 'created_date', 'modified_date',)fieldsets = ((None, {'fields': ("applicant", ("username", "city", "phone"),("email", "apply_position", "born_address", "gender", ), ("picture", "attachment",),("bachelor_school", "master_school"), ("major", "degree"), ('created_date', 'modified_date'),"candidate_introduction", "work_experience","project_experience",)}),)def save_model(self, request, obj, form, change):obj.applicant = request.usersuper().save_model(request, obj, form, change)

29 | 定制列表字段,查看简历详情

作业流程

jobs\admin.py增加ResumeDetail的详情页视图

class ResumeDetailView(DetailView):"""   简历详情页    """model = Resumetemplate_name = 'resume_detail.html'

jobs\template\resume_detail.html增加Detail页模板

{# Load the tag library #}
{% load bootstrap4 %}{# Load CSS and JavaScript #}
{% bootstrap_css %}
{% bootstrap_javascript jquery='full' %}{# Display django.contrib.messages as Bootstrap alerts #}
{% bootstrap_messages %}<h1>简历详细信息 </h1><div> 姓名: {{ object.username }} </div> <div>城市: {{ object.city }}</div> <div>手机号码: {{ object.phone }}</div><p></p>
<div>邮件地址: {{ object.email}}</div>
<div>申请职位: {{ object.apply_position}}</div>
<div>出生地: {{ object.born_address}}</div>
<div>性别: {{ object.gender}}</div>
<hr><div>本科学校: {{ object.bachelor_school}}</div>
<div>研究所学校: {{ object.master_school}}</div>
<div>专业: {{ object.major}}</div>
<div>学历: {{ object.degree}}</div>
<hr><p>候选人介绍: {{ object.candidate_introduction}}</p>
<p>工作经历: {{ object.work_experience}}</p>
<p>项目经历: {{ object.project_experience}}</p>

jobs\urls.py

    # 查看简历path('resume/<int:pk>/', views.ResumeDetailView.as_view(), name='resume-detail'),

interview\admin.py在候选人页面增加“查看简历”的链接

    def get_resume(self, obj):if not obj.phone:return ""resumes = Resume.objects.filter(phone=obj.phone)if resumes and len(resumes) > 0:return mark_safe(u'<a href="/resume/%s" target="_blank">%s</a' % (resumes[0].id, "查看简历"))return ""get_resume.short_description = '查看简历'get_resume.allow_tags = True


【Django快速开发实战】(1~29)使用Django创建一个基础应用:职位管理系统相关推荐

  1. Django企业开发实战--by胡阳,学习记录1127

    标题2.2.3 多线程版的 Web Server 1.书中讲解了引入新的参数"import time以及time.sleep()"的例子,实际应用性并不强,除非有特定的处理界面需要 ...

  2. pythondjangoweb典型模块开发实战 pdf下载_胡阳《Django企业开发实战高效Python Web框架指南》PDF及代码...

    Python社区中的框架Django 的定位是企业级开发框架,全功能 Web开发框架,少代码快速开发 Web应用.从开发速度还是上线后新功能的迭代,Django 都能很好地满足需求. 学完 Pytho ...

  3. Django企业开发实战--by胡阳,学习记录1117

    2.2.2 简单的Web协议 1.原文中的"text/plain"已经改成了"text/html",但实际重新运行的时候,浏览器依旧不会解析html文件,而是在 ...

  4. Django企业开发实战 高效Python Web框架指南 笔记 (一)

    Django企业开发实战 高效Python Web框架指南 笔记 (一) 内容: 作者是 the5fire,他的博客地址:https://www.the5fire.com/957.html 2016年 ...

  5. 送书 | 《Django项目开发实战》

    又到了周三了,说实话,本来这期想鸽的,上海这边的疫情太严重了,前几期上海这边中奖的粉丝都没办法收货,只能等解封后再安排了.在这里和大家道个歉,希望大家谅解. 最后希望在上海的小伙伴们早日渡过难关,像这 ...

  6. [Django快速开发1]搭建一个简单的博客系统(1)

    系列文章目录 Django快速开发0快速搭建环境并得到django项目的hello world 文章目录 系列文章目录 前言 从Django的模型层开始书写 定义文章模型: 使用脚本向sqlite3中 ...

  7. Django快速开发Web应用,开始项目

    Django快速开发步骤 mkdir DIR_NAME 在创建的文件目录下创建虚拟环境 python3 -m venv VENV_NAME 激活虚拟环境 - source ./activate pyt ...

  8. python使用np.logspace函数在对数刻度上创建一个对数等距数组实战:在对数刻度上创建一个数组(指定数值个数以及是否包含末尾界值)、使用不同的基数(底数)在对数刻度上构建等距数组、可视化

    python使用np.logspace函数在对数刻度上创建一个对数等距数组实战:在对数刻度上创建一个数组(指定数值个数以及是否包含末尾界值).使用不同的基数(底数)在对数刻度上构建等距数组.可视化 目 ...

  9. SAP UI5 应用开发教程之三十二 - 如何创建一个自定义 SAP UI5 控件试读版

    一套适合 SAP UI5 初学者循序渐进的学习教程 教程目录 SAP UI5 本地开发环境的搭建 SAP UI5 应用开发教程之一:Hello World SAP UI5 应用开发教程之二:SAP U ...

最新文章

  1. 研究生扩招,数据发现清华北大本科生毕生后几乎没人找工作!
  2. 《强化学习周刊》第18期:ICML-2021强化学习的最新研究与应用
  3. Science:若DTC基因检测达2%成年人群,几乎所有人的身份或将无所遁形
  4. flutter字体不跟随系统_flutter 禁止字体大小跟随系统字体改变大小
  5. vscode pylint报错的问题
  6. 面试让你手撕红黑树?30张图带你彻底理解红黑树~
  7. Redis设计与实现RDB持久化
  8. [渝粤教育] 中国地质大学 Windows程序设计 复习题 (2)
  9. mybatis学习(54):鉴定器
  10. oracle rac standby,oracle RAC数据库建立STANDBY(二)
  11. 信息收集--IP扫描 (上篇)
  12. 接口测试——Jmeter常见问题解决方法
  13. 让VS支持jQuery代码智能提示
  14. 计算机信息技术身边的具体应用案例,2016信息技术案例分析.doc
  15. python tkinter 自主小项目——计算器往事
  16. 基于javaweb+SpringBoot+JPA图书馆座位占座预约管理系统(管理员、老师、学生)
  17. 大一计算机系要什么游戏本,大一新生笔记本电脑推荐-大一新生笔记本推荐性价比高榜单...
  18. 在直播软件搭建中,如何基于rtmp实现视频直播?
  19. 大型医院HIS系统源码 优质源码 医院管理系统源码
  20. 项目经理?项目成员?

热门文章

  1. 高德导航显示白屏的问题
  2. linux 限速命令,Linux路由器限速设置详细教程
  3. 串口232,485转以太网模块 TCP/IP 串口协议转换模块
  4. 【程序员面试系列】手把手教你如何面试,你要的我都有(工作项目篇)
  5. qt对plot柱状图颜色设置
  6. 转自博客园- 林梦然+++opencv下指定文件夹下的图片灰度化(图片的读取与保存)-------简单记录
  7. eter测试软件,AcCellerator高通量单细胞力学荧光测试分析系统
  8. Error querying database. Cause: java.sql.SQLException: Error setting driver on UnpooledDataSource.
  9. windows10远程桌面连接
  10. 网络安全——SQL注入攻击