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 Job
class 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.user
super().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 render
from 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
template/joblist.html匠果科技开放职位
{% block content %} {% endblock %}
{% extends 'base.html' %}
{% block content %}
{% if job_list %}
-
{% for job in job_list %}
- {{job.type_name}} {{ job.job_name }} {{job.city_name}} {% endfor %}
No jobs are available.
{% endif %} {% endblock %} recruitment/urls.pyfrom django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path("", include("jobs.urls")),
]
jobs/urls.py
from django.urls import path
from jobs import views
urlpatterns = [
# 职位列表
path("joblist/", views.joblist, name="joblist"),
]
http://127.0.0.1:8000/joblist/
08 | 添加自定义页面:让匿名用户可以查看职位详情
template/job.html
{% extends 'base.html' %}
{% block content %}
{% if job %}
岗位名称:{{job.job_name}}
城市:
{{job.city_name}}
岗位职责:
{{job.job_responsibility}}
任职要求:
{{job.job_requirement}}
{% else %}
职位不存在
{% endif %}
{% endblock %}
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 views
urlpatterns = [
# 职位列表
path("joblist/", views.joblist, name="joblist"),
# 职位详情
#url(r'^job/(?Pd+)/$', views.detail, name='detail'),
path('job//', views.detail, name='detail'),
]
09 | 开始一个正式的产品:产品背景、迭代思维与MVP产品规划
10 | 唯快不破:在产品中使用产品迭代思维
11 | 数据建模 & 企业级数据库设计原则
12 | 创建应用和模型,分组展示页面内容
两个需求可以合二为一:1)HR可以维护候选人信息;2)面试官可以维护面试反馈。
interview/models.pyfrom django.db import models
from django.contrib.auth.models import User
from 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
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官方文档链接
新增interviewmanagementcommandsimport_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.csv
class 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:UsersSeasonDesktop4.python网页前后端Django基础教程workspace4-recruitingrecruitment【13-】>python manage.py import_candidates --path candidates.csv 刘倩 陈欣欣 刘德华 李小龙http://127.0.0.1:8000/admin/interview/candidate/
admin管理界面可以看到批量导入的名单。
新增以下代码
# 右侧筛选条件
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 | 批量设置面试官:面试官的导入、授权(略)
17 | 产品新需求 :如何导出候选人的数据到CSV(增加自定义的数据操作菜单)
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_fields
response['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)
return response
export_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/



