Django笔记——03 实现招聘评估系统

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文件内容格式为:
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)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值