1、产品背景与迭代思维
MVP:最小可用产品,能让业务运行起来的最小功能子集
就是说先实现当前版本中最核心最重要的功能,然后一步步进行功能的迭代
如何找出产品的MVP功能范围:
- 确定产品核心目标,核心用户,核心场景
- 产品目标是否需要在线上完成或呈现
- 最小MVP产品要做哪些事情能够达到业务目标
- 哪些功能不是在用户流程的核心路径上
- 做哪些简化和假设能够在最短时间内交付产品,让业务流程跑起来
2、企业级数据库设计十个原则
1、3个基础原则
- 结构清晰:表名,字段名没有歧义,能一眼看懂
- 唯一职责:一表一用,领域定义清晰,不存储无关信息,相关数据在一张表中
- 主键职责:设计不带物理意义的主键,有唯一约束,确保幂等
2、4个扩展性原则(影响系统的性能和容量)
- 长短分离:可以扩展,长文本独立存储,有合适的容量设计,通常吧长文本存储在KT系统中
- 冷热分离:当前数据与历史数据分离
- 索引完备:有合适索引方便查询
- 不相关联查询:不使用一切的SQLJoin操作,不做2个表或者更多表的关联查询
3、3个完备性原则
- 完整性:保证数据的准确性和完整性,主要的内容都有记录
- 可追溯:可追溯创建时间,修改时间,可以逻辑删除
- 一致性原则:数据之间保持一致,尽可能避免同样的数据存储在不同表中
3、model中__unicode__和__str__的使用
#python2优先使用这个方法,把对象转换成字符串,如果没有__unicode__()方法,使用__str__()方法
def __unicode__(self):
return self.username
#python3 直接定义__str__()方法即可,系统使用这个方法来把对象转换成字符串
def __str__(self):
return self.username #定义视图显示的字段名
4、admin中详情页面的分组
# 分组,fieldsets是字段的集合列表;元组第一个元素表示分组展示的名字;第二个元素是一个map,定义了有哪些字段
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_general_ability", "paper_score"),"last_editor",)}),
('第一轮面试记录', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage", ("first_result", "first_recommend_position"), ("first_interviewer", "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", "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", "hr_remark"),)})
)
5.批量从Excel文件中导入候选人数据
创建一文件名为import_candidates
class Command(BaseCommand):
help = '从一个CSV文件的内容中读取候选人列表,导入到数据库中' # 帮助的文字
#命令行参数
def add_arguments(self, parser):
parser.add_argument('--path', type=str) # 使用长命令行,Linux命令中通用用法,--表示长命令,参数传进来是一个字符串类型
# 处理逻辑
def handle(self, *args, **options):
path = options['path'] # 读取路径
with open(path,'rt',encoding='gbk') as f: # 用只读方式打开文件
reader = csv.reader(f,dialect='excel',delimiter=';') # 用csv读文件;dialect:指定的格式,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_general_ability=row[6],
paper_score=row[7]
)
print(candidate)
在终端上输入命令:python manage.py import_candidates --path ./candidates.csv 即可导入csv内容
csv文件内容格式为:
6、列表筛选和查询
在admin中操作
# 列表页搜索属性
search_fields = ('username', 'phone', 'email', 'bachelor_school')
#筛选条件(展示页坐标出现一个筛选块)
list_filter = ('city','first_result','hr_result','first_interviewer_user','second_interviewer_user','hr_interviewer_user')
# 列表页排序
ordering = ('hr_result', 'second_result', 'first_result')
7、将数据导出为csv文件
在admin中定义一个函数export_model_as_csv
# 实现数据导出功能
exportable_fields = ('username', 'city', 'phone', 'bachelor_school', 'master_school', 'degree', 'first_result', 'first_interviewer',
'second_result', 'second_interviewer', 'hr_result', 'hr_score', 'hr_remark', 'hr_interviewer')
# request是用户发起的请求,queryset是用户在界面上选择的结果列表里面的数据集合
def export_model_as_csv(modeladmin,request,queryset): # 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:
#单行记录(各个字段的值)写入到csv文件
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文件'
将函数注册到actions中,哪个actions就注册在哪个继承ModelAdmin的函数中
actions = [export_model_as_csv,]
8、记录日志,方便排查问题
四个组件
- Loggers:日志记录的处理类/对象,一个Logger可以有多个Handlers
- Handler:对于每一条日志消息如何处理,记录到文件,控制台,还是网络
- Filters:定义过滤器,用于Logger/Handler之上
- Formmaters:定义日志文本记录的格式
四个日志级别
- DEBUG:调试
- INFO:常用的系统信息
- WARNING:小的警告,不影响主要功能
- ERROR:系统出现不可忽视的错误
- CRITICAL:非常严重的错误
# 日志记录
LOGGING = {
"version": 1, # 日志记录格式的版本号
"disable_existing_loggers":False, # 是不是禁用现在已有的Logger
"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(BASE_DIR, 'recruitment.admin.log'), #BASE_DIR的上一个目录,即项目目录的上一个目录的下面
},
'performance': {
#'level': 'INFO',
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': os.path.join(BASE_DIR, 'recruitment.performance.log'),
},
},
'root': { #系统全局级别默认的日志记录器,属于Logger里面的一种特殊记录器
'handlers': ['console', 'file'], # 控制台和文件同时输出
'level': 'INFO',
},
"interview.performance": {
"handlers": ["console", "performance"],
"level": "INFO",
"propagate": False,
},
}
自定义日志
定义Logger类
loggng=logging.getLogger(__name__)
在需要写入日志的函数里面写,用导出csv文件的函数举例:
logging.info('%s exported %s candidate records' % (request.user, len(queryset)))
9、生产环境与开发环境配置分离
问题:
- 生产环境的配置与开发环境配置隔离开,开发环境允许Debugging
- 敏感信息不提交到代码库中,比如数据库连接,secret key, LDAP连接信息等
- 生产、开发环境使用的配置可能不一样,比如分别使用MySQL/Sqlite数据库
这些信息可以放到开发环境的配置里面去,同时把开发环境的配置从.gitignore里面去把它指定出来
可以创建在项目目录下创建一个settings包,将settings.py的配置放到settings包里面,将文件改名为base.py,settings包里面还需要创建一个__init__.py的文件
原先引用配置是在manage.py里面引用的,manage.py里面有一个setdefault,设置了DJANGO_SETTINGS_MODULE指向recruitment包下面的settings的配置,所以要改成settings包下面的base.py
当我们启动django环境,默认没用指定django配置的时候,就使用settings.base
在settings.base里面放有基础的配置,创建local.py和production.py的配置文件,可以将local.py条件到.gitignore中
指定配置:
python manage.py runserver 0.0.0.0:8000 --settings=settings.local
指定的配置会去覆盖base里面的配置
10、完善页面
标题,在urls中定义:
from django.utils.translation import gettext as _
admin.site.site_header = _('匠果科技招聘管理系统')
在model字段中用help_text来设置提示内容
实现由HR决定面试官是谁,面试官不能改动HR的决定
将面试官改为选项:
second_interviewer_user = models.ForeignKey(User, related_name='second_interviewer_user', blank=True, #引用外键,所以变为choice
null=True, on_delete=models.CASCADE,
verbose_name=u'面试官')
对于面试官而言,面试官字段只能可读:
#第一种做法
#设置只读字段,详细页的,这个是面对所有用户的方法
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_names
def get_readonly_fields(self, request, obj=None):
group_names = self.get_group_names(request.user) # 获取用户所处群组
if '面试官' in group_names:
logging.info("interviewer is in user's group for %s" % request.user.username)
return ('first_interviewer_user','second_interviewer_user')
return ()
让HR能够批量地在列表页进行修改
# 第一种方式,对所有用户的
#数据表单页面的superuser可修改选项
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 ('first_interviewer_user','second_interviewer_user',)
return ()
# 使用get_list_editable返回一个列表django是不认的,因为django对list_editable没有相应的函数来支持这个属性的设置,所以覆盖ModelAdmin里面定义的一个get_changelist_instance,覆盖掉父类list_editable的属性
def get_changelist_instance(self, request): #覆盖父类的list_editable属性
self.list_editable = self.get_list_editable(request)
return super(CandidateAdmin, self).get_changelist_instance(request)