1、遗留系统集成
-
问题:
已经有内部系统在运行了,缺少管理功能,希望能有一个权力后台,比如:人事系统,CRM,ERP的产品,缺少部分数据的维护功能 -
诉求:
3分钟生成一个管理后台;可以灵活定制页面;不影响正在运行的业务系统
为已有数据库生成管理后台
- 创建项目: $ django-admin startproject empmanager
- 编辑 settings.py 中的数据库配置, vim ~/settings.py
DATABASES = {
'default’: {
'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydatabase',
'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT':
'5432',
} }
- 生成 model 类: ./manage.py inspectdb > models.py
- 创建一个空的项目 empmanager
- 进入settings.py修改DATABASES ,改成生产环境的数据库
- 创建一个应用candidates
- 用python manage.py inspectdb 生成model
- python manage.py inspectdb > candidates/models.py 导出到models中去
- 也可以在manage.py的时候指定相应的表,比如:python manage.py inspectdb candidate 回车,就只把candidate这张表打印出来;python manage.py inspectdb candidate,jobsjob 也可以选择多张表
2、Django的中间键(Middleware)
中间键时注入在Django 请求/响应 处理流程中的钩子框架, 能对request/response 作处理
广泛的使用场景:
- 登陆认证,安全拦截
- 日志记录,性能上报
- 缓存处理,监控告警
自定义中间件的2种方法:
- 使用函数实现
- 使用类实现
函数实现:
def simple_middleware(get_response):
# One-time configuration and initialization
def middleware(request):
# Code to be executed for each request before
# the view (and later middleware) are called
response = get_response(request)
# Code to be executed for each request/response after
# the view is called
return response
return middleware
类实现:
Django提供的get_response方法
可能是一个真实的视图,也可能是请求处理链中的下一个中间件
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called
return response
在处理的过程中,既有request的请求,又有最后返回的response对象,在这个过程中可以对request和response进行处理。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 安全的中间件,做通常的安全拦截处理
'django.contrib.sessions.middleware.SessionMiddleware', # Session的中间件,处理用户的登陆信息
'django.middleware.common.CommonMiddleware', # Common中间件,常用的处理
'django.middleware.csrf.CsrfViewMiddleware', # Csrf中间件,用来处理跨站攻击
'django.contrib.auth.middleware.AuthenticationMiddleware', # 处理用户的认证登陆
'django.contrib.messages.middleware.MessageMiddleware', # 用来处理消息,当用户操作时候,需要给他什么提示时,用这个把消息传递到用户的页面上
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
3、创建请求日志、性能日志记录的中间件
中间件用来记录用户访问的url,访问url的时候传过来的参数以及url处理过程中耗费了多少时间,记录到日志文件中
- 定义实现中间件: def performance_logger_middleware(get_response)
- 记录请求 URL, 参数, 响应时间
- 注册 middleware 到 settings 中
- 配置 日志文件路径
1、创建一个文件performance.py
logger = logging.getLogger(__name__)
def performance_logger_middleware(get_response):
def middleware(request):
start_time = time.time() # 在处理请求之前把开始时间记录下来
response = get_response(request) # get_response对接收到的请求做处理
duration = time.time() - start_time # 处理完成后的时间-开始时间得到耗时
response["X-Page-Duration-ms"] = int(duration * 1000) # 耗时通过response的头返回出去,在浏览器inspect工具,可以看到浏览器里面页面访问耗时
logger.info("%s %s %s", duration, request.path, request.GET.dict()) # 记录到日志中
return response # 处理完之后吧response返回
return middleware
2、添加到settings的MIDDLEWARE 里面
中间件是从上到下依次执行的,由于是记录耗时,所以该中间件应该放在第一个位置
MIDDLEWARE = [
'interview.performance.performance_logger_middleware'
...
]
3、在settings的LOGGING中添加日志记录配置
interview.performance与performance
# 日志记录
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'),
},
'performance': {
#'level': 'INFO',
'class': 'logging.FileHandler',
'formatter': 'simple',
'filename': os.path.join(BASE_DIR, 'recruitment.performance.log'),
},
},
'root': {
'handlers': ['console', 'file'],
'level': 'INFO',
},
'loggers': {
"interview.performance": {
"handlers": ["console", "performance"],
"level": "INFO",
"propagate": False,
},
},
}
4、在Django中支持多语言
步骤:
- 代码中使用 gettext, gettext_lazy 函数获取多语言资源对应的文本内容
- 生成多语言资源文件
- 翻译多语言内容
- 生成二进制多语言资源文件
- Model,以及 Django 的 python 代码里面使用多语言
from django.utils.translation import gettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
- 生成文本格式的多语言资源文件 .po 文件
- django-admin makemessages -l zh_HANS -l en
- 翻译 .po 文件中的内容到不同语言
- 编译生成可以高效使用的二进制文件 (.mo) 文件
- django-admin compilemessages
模板导入{% load i18n %}
用translate 指令来获取某一个key对应的资源
{% translate "匠果科技开放职位" %}
当需要翻译的字符串里面包含变量的时候,或者多段文本的时候,可以使用blocktranslate
{% blocktranslate with user_name=user.username %} 终于等到你 {{ user_name }}, 期待加入我们,用技术去探索一个新世界 {% endblocktranslate %}
生成多语言文件
使用命令之前要确保工程目录下面有一个local文件,local是用来存放多语言资源文件的
在终端运行的:-l来指定语言
django-admin makemessages -l zh_HANS -l en
把多语言编译成二进制格式
django-admin compilemessages
这时会生成po文件
url添加多语言url支持
path('i18n/', include('django.conf.urls.i18n')),
在settings里面添加多语言的配置
LANGUAGES = [
('zh-hans', _('Chinese')),
('en', _('English')),
]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
中间件添加:
'django.middleware.locale.LocaleMiddleware',
在HTML中添加form表单让用户选择语言
<form action="{% url 'set_language' %}" method="post" style="margin-block-end: 0em;">{% csrf_token %}
<input name="next" type="hidden" value="{{ redirect_to }}">
<select name="language">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
<input type="submit" value={% translate "Switch" %} style="font-size:12;height:20px">
</form>
5、防止XSS跨站脚本攻击
加一个有XSS安全漏洞的页面
'''
直接返回 HTML 内容的视图 (这段代码返回的页面有 XSS 漏洞,能够被攻击者利用)
'''
def detail_resume(request, resume_id):
try:
resume = Resume.objects.get(pk=resume_id)
content = "name: %s <br> introduction: %s <br>" % (resume.username, resume.candidate_introduction)
return HttpResponse(content)
except Resume.DoesNotExist:
raise Http404("resume does not exist")
在url中:
if settings.DEBUG: # 只有开始DEBUG才将url添加到urlpatterns中去,在生产环境下这个url是访问不到的
# 有XSS漏洞的视图
urlpatterns += [
url(r'^detail_resume/(?P<resume_id>\d+)/$', views.detail_resume, name='detail_resume'),
]
XSS攻击过程:
正常用户访问页面,在访问页面填入数据的时候,往里面填入一段JavaScript脚本,当其他用户访问这个人的个人信息时,里面含有这段代码的信息就会自动执行,是以访问者的身份来调用代码
访问:http://127.0.0.1:8000/detail_resume/4/
其他视图用的是render函数返回一个页面内容,然后页面里面把请求的request带上,用render时,django会自动把HTML的内容转译,所有的脚本都不会再执行,所以要解决XSS,可以在HttpResponse那里做一个转译
# 该视图用来访问一个简历的详情
def detail_resume(request, resume_id):
try:
resume = Resume.objects.get(pk=resume_id)
content = "name: %s <br> introduction: %s <br>" % (resume.username, resume.candidate_introduction)
return HttpResponse(html.escape(content))
except Resume.DoesNotExist:
raise Http404("resume does not exist")
6、CSRF跨站请求伪造和SQL注入攻击
- CSRF(Cross-site request forgery,简称:CSRF 或 XSRF)
- 恶意攻击者在用户不知情的情况下,使用用户的身份来操作
- 黑客的准备步骤
- 黑客创建一个 请求网站 A 类的 URL 的 Web 页面,放在恶意网站 B 中 ,这个文件包含了一个创建
用户的表单。这个表单加载完毕就会立即进行提交。 - 黑客把这个恶意 Web 页面的 URL 发送至超级管理员,诱导超级管理员打开这个 Web 页面。
- 黑客创建一个 请求网站 A 类的 URL 的 Web 页面,放在恶意网站 B 中 ,这个文件包含了一个创建
模拟攻击:
创建一个HTML页面让管理员能够添加HR账号
view里面添加一个create_hr_user的视图
# 这个 URL 仅允许有 创建用户权限的用户访问
@csrf_exempt # 这个标记表示视图不去处理csrf的攻击
@permission_required('auth.user_add') # 这个页面是有权限创建用户的用户才能进入,表示使用这个用户需要有添加用户的权限
def create_hr_user(request):
if request.method == "GET":
return render(request, 'create_hr.html', {}) # 如果是get请求,则将create_hr这个页面展示出来
if request.method == "POST": # 如果是post请求
username = request.POST.get("username") # 取到用户密码
password = request.POST.get("password")
hr_group = Group.objects.get(name='HR') # 取到hr这个群组
user = User(is_superuser=False, username=username, is_active=True, is_staff=True) # 创建一个用户
user.set_password(password)
user.save() # 保存创建用户
user.groups.add(hr_group) # 将用户设置为HR角色
messages.add_message(request, messages.INFO, 'user created %s' % username) # 添加消息
return render(request, 'create_hr.html') # 渲染表单
return render(request, 'create_hr.html')
url
# 管理员创建 HR 账号的 页面:
path('create_hr_user/', views.create_hr_user, name='create_hr_user'),
ps:这段代码有很多额外知识点需要知道,比如权限的标记permission_required,还有用户的创建
user = User(is_superuser=False, username=username, is_active=True, is_staff=True) # 创建一个用户
还有创建用户添加群组
user.groups.add(hr_group) # 将用户设置为HR角色
还有添加消息
messages.add_message(request, messages.INFO, 'user created %s' % username) # 添加消息
模拟攻击过程:
黑客可以在自己的网站上创建一个页面,这个页面提供一个url,诱导管理员访问这个url,管理员访问这个url的时候,url自动提交,自动提交的时候,url会调用之前的招聘管理系统页面,去直接创建一个账号
取消这个标记,并且在HTML的form表单下面添加{% csrf_token %}即可
@csrf_exempt # 这个标记表示视图不去处理csrf的攻击
视图里面的render方法,带有request,context,会把csrf_token 服务端产生的token传到客户端的HTML页面上,用户在浏览器里提交请求,会自动吧token带回到服务端,服务端收到这个token的时候,会去做校验,校验通过,是服务端产生的,则认为该请求合法。
SQL注入攻击
7、Django与Celery集成
- Celery一个分布式的任务队列
- 简单: 几行代码可以创建一个简单的 Celery 任务
- 高可用:工作机会自动重试
- 快速:可以执行一分钟上百万的任务
- 灵活:每一块都可以扩展
Celery是一个分布式的任务队列,把大量的任务分布到不同的机器上去,通过集群来运行大量的任务
8、文件图片的上传
场景/目标:
- 投递简历的页面, 可以上传个人的照片, 以及附件简历
- 上传的文件存储在服务器上,文件服务可以扩展
存储方案选型
- 使用服务器本地磁盘
- 自建分布式文件服务器
- 阿里云 OSS
使用本地存储的操作过程
1、设置图片、文件存储路径 & URL 映射
- settings 里面添加 /media 路径, urls.py 中添加图片路径映射
2、 准备 model, form, view 和 HTML 表单模板
- model 里面添加图片/文件字段(如 个人照片, 个人简历字段到 Resume)
- form.py 中增加图片,附件字段
- 创建简历的视图中展示 picture, attachment 字段
- HTML 表单模板中增加 enctype 属性 (resume_form.html )
3、 变更数据库
4、 Admin里面 添加展示字段, 简历列表中加上照片展示
使用阿里云 OSS 存储:
- 安装 OSS 库
- OSS 的依赖添加 django_oss_storage 到 APPS
- settings 里面添加 OSS 设置
使用本地存储:
在settings:这两个属性是上传图片,上传文件的资源所存放的路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
urls:document_root是文档的路径,加到静态资源里去,然后添加到urlpatterns 中来
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
model:添加两个字段
picture = models.ImageField(upload_to='images/', blank=True, verbose_name=_('个人照片'))
attachment = models.FileField(upload_to='file/', blank=True, verbose_name=_('简历附件'))
form.py
class ResumeForm(ModelForm):
class Meta:
model = Resume
fields = ["username", "city", "phone",
"email", "apply_position", "born_address", "gender", "picture", "attachment",
"bachelor_school", "master_school", "major", "degree",
"candidate_introduction", "work_experience", "project_experience"]
HTML:enctype指定最后传输的数据的编码格式是用form-data来传输,这样文件图片就能上传了
<form method="post" method="post" class="form" enctype="multipart/form-data" style="width:600px;margin-left:5px">
</form>
admin:
# 将picture展示在列表中
def image_tag(self, obj):
if obj.picture: # 将picture这个字段做格式化,它是一个url
return format_html('<img src="{}" style="width:100px;height:80px;"/>'.format(obj.picture.url)) # 将拿到的url用HTML封装起来
return ""
image_tag.allow_tags = True
image_tag.short_description = 'Image'
OSS 存储:
安装:
pip install django-oss-storage
django-oss-storage添加到应用中
settings:
# 阿里云 CDN 存储静态资源文件 & 阿里云存储上传的图片/文件
# STATICFILES_STORAGE = 'django_oss_storage.backends.OssStaticStorage'
DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage' # django会自动把文件存储到oss中
# AliCloud access key ID
OSS_ACCESS_KEY_ID = os.environ.get('OSS_ACCESS_KEY_ID','')
# AliCloud access key secret
OSS_ACCESS_KEY_SECRET = os.environ.get('OSS_ACCESS_KEY_SECRET','') # 访问oss需要一个AKSK
# The name of the bucket to store files in
OSS_BUCKET_NAME = 'djangorecruit' # 使用哪个BUCKET
# The URL of AliCloud OSS endpoint
# Refer https://www.alibabacloud.com/help/zh/doc-detail/31837.htm for OSS Region & Endpoint
OSS_ENDPOINT = 'oss-cn-beijing.aliyuncs.com' # 使用的哪边的数据中心
DINGTALK_WEB_HOOK_TOKEN = os.environ.get('DINGTALK_WEB_HOOK_TOKEN','')
DINGTALK_WEB_HOOK = "https://oapi.dingtalk.com/robot/send?access_token=%s" % DINGTALK_WEB_HOOK_TOKEN
9、多数据库路由
- 多数据库配置
- 指定数据库表生成 model (inspectdb)
- 注册到 Admin 中 (running/admin.py)
- 添加 Router 类 & settings 中配置 Router
settings:
DATABASES = {
'default': {
#'ENGINE': 'django.db.backends.sqlite3',
'ENGINE': 'django_prometheus.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
},
# 'running': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'running',
# 'USER': 'recruitment',
# 'PASSWORD': 'recruitment',
# 'HOST': 'running',
# 'PORT': '3306',
# },
}
#DATABASE_ROUTERS = ['settings.router.DatabaseRouter'] #路由的配置
在settings文件夹里面创建的一个py文件:定义了数据库表的路由
class DatabaseRouter:
route_app_labels = {'running'} #应用的label
def db_for_read(self, model, **hints): #读操作时访问哪个数据库
if model._meta.app_label in self.route_app_labels:
return 'running'
return 'default'
def db_for_write(self, model, **hints): #写操作时返回哪个数据库
if model._meta.app_label in self.route_app_labels:
return 'running'
return 'default'
def allow_relation(self, obj1, obj2, **hints): # 是不是允许表之间有相互的关系
return None
def allow_migrate(self, db, app_label, model_name=None, **hints): # model跟数据库之间允不允许做同步做迁移
"""
遗留数据库中的表不允许迁移
"""
if app_label in self.route_app_labels:
return False
return True
terminal:
python manage.py inspectdb --database=running --settings=settings.local
10、3行代码支持大数据量的关联外键
admin:
@admin.register(Country) #相当于注册 admin.site.register
class CountryAdmin(admin.ModelAdmin):
search_fields = ('chn_name', 'eng_name',)
@admin.register(Province)
class ProvinceAdmin(admin.ModelAdmin):
search_fields = ('chn_name', 'eng_name',)
@admin.register(City)
class CityAdmin(admin.ModelAdmin):
autocomplete_fields = ['provinceid','countryid',]
list_display = ('cityid', 'countryid', 'areaid', 'provinceid', 'chn_name', 'eng_name')
多级关联
11、20行代码实现只读站点ReadOnlyAdmin
- 成遗留的已有系统
- 已有系统的数据涉及到核心数据
- 为了确保数据安全,管理后台只提供数据的浏览功能
- 设置列表页 list_display 展示所有字段
admin:
class ReadOnlyAdmin(admin.ModelAdmin):
readonly_fields = []
def get_list_display(self, request): # 默认所有字段都返回到list_display显示出来
return [field.name for field in self.model._meta.concrete_fields]
def get_readonly_fields(self, request, obj=None):
return list(self.readonly_fields) + \
[field.name for field in obj._meta.fields] + \
[field.name for field in obj._meta.many_to_many] #外键依赖关系
def has_add_permission(self, request): # 添加数据的权限
return False
def has_delete_permission(self, request, obj=None): # 删除数据的权限
return False
def has_change_permission(self, request, obj=None): # 更改数据的权限
return False
# 继承只读的类
@admin.register(Country) #相当于注册 admin.site.register
class CountryAdmin(ReadOnlyAdmin):
search_fields = ('chn_name', 'eng_name',)
@admin.register(Province)
class ProvinceAdmin(ReadOnlyAdmin):
search_fields = ('chn_name', 'eng_name',)
@admin.register(City)
class CityAdmin(ReadOnlyAdmin):
autocomplete_fields = ['provinceid','countryid',]
list_display = ('cityid', 'countryid', 'areaid', 'provinceid', 'chn_name', 'eng_name')
12、10行代码自动注册所有model到管理后台
主应用,apps.py
class AdminClass(admin.ModelAdmin):
def __init__(self, model, admin_site):
# 列表页自动显示所有的字段:
self.list_display = [field.name for field in model._meta.fields]
super(AdminClass, self).__init__(model, admin_site)
# automatically register all models
class UniversalManagerApp(AppConfig):
"""
应用配置在 所有应用的 Admin 都加载完之后执行
"""
# the name of the AppConfig must be the same as current application
name = 'recruitment'
def ready(self):
models = apps.get_app_config('running').get_models() # 遍历running下面的所有model
for model in models:
try:
admin.site.register(model, AdminClass)
except admin.sites.AlreadyRegistered:
pass
UniversalManagerApp添加到settings的应用中去(是路径)
可以用type函数动态的定义一个类,name是传入一个名称定义了一个model类名,中间是继承自哪些类
13、Signals信号
- Signals是Django 的信号
- Django 框架内置的信号发送器,这个信号发送器在框架里面
- 有动作发生的时候,帮助解耦的应用接收到消息通知
- 当动作发生时,允许特定的信号发送者发送消息到一系列的消息接收者
- Signals 是同步调用
所有的 Signals 都是 django.dispatch.Signal 的实例/子类
- django.db.models.signals.pre_init 模型实例初始化前
- django.db.models.signals.post_init 模型实例初始化后
- django.db.models.signals.pre_save 模型保存前
- django.db.models.signals.post_save 模型保存后
- django.db.models.signals.pre_delete 模型删除前
- django.db.models.signals.post_delete 模型删除后
- django.db.models.signals.m2m_changed 多对多字段被修改
- django.core.signals.request_started 接收到 HTTP 请求
- django.core.signals.request_finished HTTP 请求处理完毕
自定义信号
1)定义信号: 在项目根目录新建文件self_signal.py
import django.dispatch
my_signal = django.dispatch.Signals(providing_args=["argument1","argument2"])
2)触发信号:业务逻辑中触发信息
from self_signal import my_signal
my_signal.send(sender="Recruitment", argument1=111, argument2=2)
3)注册信号处理器/接收器
from self_signal import my_signal
my_signal.connect(callback_of_my_signal)
14、django常用插件
- Django debug toolbar : 提供一个可以查看debug 信息的面板(包括SQL执行时间,页面耗时)
- django-silk :性能瓶颈分析
- Simple UI:基于Element UI 和 VUE 的 Django Admin 主题
- Haystack Django :模块化搜索方案
- Django notifications: 发送消息通知,你有 xx 条未处理简历
- Django markdown editor :Markdown 编辑器
- django-crispy-forms : Crispy 表单,以一种非常优雅、干净的方式来创建美观的表单
- django-simple-captcha:Django表单验证码
- Django debug toolbar : 提供一个可以查看debug 信息的面板(包括SQL执行时间,页面耗时)
- https://django-debug-toolbar.readthedocs.io/