问题:
已经有内部系统在运行了,缺少管理功能,希望能有一个权力后台,比如:人事系统,CRM,ERP的产品,缺少部分数据的维护功能
诉求:
3分钟生成一个管理后台;可以灵活定制页面;不影响正在运行的业务系统
DATABASES = {
'default’: {
'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydatabase',
'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT':
'5432',
} }
中间键时注入在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',
]
中间件用来记录用户访问的url,访问url的时候传过来的参数以及url处理过程中耗费了多少时间,记录到日志文件中
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,
},
},
}
步骤:
from django.utils.translation import gettext_lazy as _
class MyThing(models.Model):
name = models.CharField(help_text=_('This is the help text'))
模板导入{% 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表单让用户选择语言
加一个有XSS安全漏洞的页面
'''
直接返回 HTML 内容的视图 (这段代码返回的页面有 XSS 漏洞,能够被攻击者利用)
'''
def detail_resume(request, resume_id):
try:
resume = Resume.objects.get(pk=resume_id)
content = "name: %s
introduction: %s
" % (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\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
introduction: %s
" % (resume.username, resume.candidate_introduction)
return HttpResponse(html.escape(content))
except Resume.DoesNotExist:
raise Http404("resume does not exist")
模拟攻击:
创建一个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注入攻击
Celery是一个分布式的任务队列,把大量的任务分布到不同的机器上去,通过集群来运行大量的任务
场景/目标:
存储方案选型
使用本地存储的操作过程
1、设置图片、文件存储路径 & URL 映射
2、 准备 model, form, view 和 HTML 表单模板
3、 变更数据库
4、 Admin里面 添加展示字段, 简历列表中加上照片展示
使用阿里云 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来传输,这样文件图片就能上传了
admin:
# 将picture展示在列表中
def image_tag(self, obj):
if obj.picture: # 将picture这个字段做格式化,它是一个url
return format_html(''.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
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
@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')
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')
主应用,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类名,中间是继承自哪些类
所有的 Signals 都是 django.dispatch.Signal 的实例/子类
自定义信号
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)