admin后台维护模型
- 创建模型
- 在应用的admin模块注册模型 admin.site.register(模型类[, 模型管理类])
- 可以在根urls模块设置admin.site.site_header等信息,修改后台显示的系统名称
模型admin类
- 在应用admin模块为模型类创建对应的模型管理类,继承admin.ModelAdmin
- 通过模型管理类可以进行定制话管理
- exclude:在编辑页面中不显示的模型字段
- list_display:在模型列表页显示的模型字段,元素可以是模型字段名,或者模型admin里面的函数名,如果是函数名,展示的就是函数返回的字符串,还可以使用mark_safe返回 < a > 的html内容,这样就可以生成跳转链接了
- fieldsets:可以对表单字段设置分组(('分组名称', {'fields': (本组的字段,可以嵌套一层集合,表示那些字段绘制在同一行)}),
(第二组...)...),对应的方法是get_fieldsets(self, request, obj=None)
- list_filter:用来过滤的字段
- search_fields:用来搜索查询的字段
- ordering:设置可以排序的字段
- readonly_fields:可以设置admin后台表单中只读的字段,对应的方法是get_readonly_fields(self, request, obj),这样可以根据当前登录用户、角色等条件进行定制处理
- list_editable:在列表页面可编辑的字段,这个字段没有对应的方法,但是可以在get_changelist_instance(self, request)中对进行动态设置:self.ediable = xxx
- get_queryset():列表页默认调用此方法获取数据,可以在此方法中根据登录用户、角色等条件进行定制处理
- save_model():模型保存时会自动调用此方法,可以做一些特殊处理,比如自动给用户字段赋值,自动给给更新时间字段赋值等
权限设置
- 可以在模型的Meta类中,通过permissions属性给模型自定义权限(定义完权限,需要makemigrations、 migrate迁移操作),
- 然后在模型admin中,通过 功能函数.allowed_permissions = ('xxx', 'xxx', ...) 把功能与权限进行关联,
- 同时需要实现 has_xxx_permission(self, request) 方法,判断用户是否有对应的权限,方法名中xxx对应的就是模型permissions中设置的权限名称,
- 然后在admin后台的权限管理里面,就可以给group或者user设置对应的权限了
系统自带用户模型User
- 判断是否为超级用户:user.is_superuser
- 获取用户所属group:user.groups.all()
自定义django命令
- 可以在应用下创建目录 management/commands ,然后在目录下新增模块,并在模块中定义Command类,继承BaseCommand类,这样,模块名就是命令名,可以使用python manage.py 模块名 xxx 来执行自定义命令
多语言设置
- 代码中使用gettext或者gettext_lazy获取多语言资源对应的文本内容
- 可以使用 # Translators: 注释内容 这样在生成资源文件的时候会带上这段注释内容
- 在模板中使用 {% translate "xxx" %}获取多语言文本
- 在模板中使用 {% blocktranslate with xxx=yyy%} {{xxx}}
- 生成多语言资源文件
- 在工程目录下创建locale目录
- 使用命令创建:django-admin makemessages -l zh_HANS -l en 需要更多语言就继续添加 -l xxx
- 这样在locale目录下会生成zh_HANS和en两个种语言的目录
- 翻译多语言内容
- 生成二进制多语言资源文件
- django-admin compilemessages 生成二进制语言文件
- 在settings中添加多语言配置
from django.utils.translation import gettext_lazy as _
# 系统支持的语言种类
LANGUAGES = [
('zh-hans', _('Chinese')),
('en', _('English')),
]
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# 多语言文件路径
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
- 在页面中提供语言切换功能
- 在主urls模块添加路由:path('i18n/', include('django.conf.urls.i18n'))
- 添加路由中间件
'django.contrib.sessions.middleware.SessionMiddleware', # 它之后
'django.middleware.locale.LocaleMiddleware', # 加这个
'django.middleware.cache.UpdateCacheMiddleware', # 它之前
<form action="{% url 'set_language' %}" method="post" > ... </form>
其中set_language是在i18n路由中提供的
- django识别语言类型的顺序:url > cookie > 请求头 > settings里面设置的LANGUAGE_CODE
安全
XSS跨站脚本攻击
- 用户在网页中提交了带有js脚本的文本,比如评论信息
- 服务端没有做处理,之间把带js脚本的数据保存到了数据库
- 当有其他用户访问到还有对应数据的页面时,js脚本将以当前访问用户的身份被执行
- django的处理方式:
- 模板使用html文件,通过使用render函数进行加载渲染后返回
- 如果要直接返回字符串,可使用python标准库html,然后使用html.escape(xxx)对xxx字符串进行处理,这样界面就会直接展示js脚本,而不是被浏览器js引擎加载执行
- 对用户提交的数据进行校验,避免提交不合法数据
CSRF跨站请求伪造
- 黑客在自己的网站中通过链接、表单等方式链接到了A网站
- 用户访问到黑客的网站是,被诱导点击了带有访问A网站的链接或者表单(用户不知道背后请求的是A网站)
- 这时,浏览器就会携带用户在A网站的cookie请求A网站,这种情况下,黑客就可以以用户的身份对A网站进行操作,比如银行转账等等
- 所有,如果在浏览器中登录了银行之类的网站,最好及时登出,一般登出的时候,会清理掉cookie等信息
- django的处理方式:
- 添加中间件django.middleware.csrf.CsrfViewMiddleware,默认是添加好的
- 在模板表单中使用{% csrf_token %},这样就会在渲染表单时,添加隐藏的input,比如:
<input type="hidden" name="csrfmiddlewaretoken" value="Lw0GbBUXucFQNnPkdLCnfrirdY3UqvdfLwZPH00DNEU9Iu9nYyvcIYQH4FY6C1qF">
- 同时浏览器的中会存在名称为csrftoken的cookie,
- 表单提交的时候就会在表单数据中传递csrfmiddlewaretoken到服务器,同时请求的cookie中会带上csrftoken
- 中间件django.middleware.csrf.CsrfViewMiddleware在处理请求的时候就会判断表单csrfmiddlewaretoken与cookie csrftoken的值是否一致
- 目前新版本的csrfmiddlewaretoken的值是64位的,前32位是mask,后32位是token,以token的每一个字符减去对应的mask位置的字符的差作为索引,到字符字典中查对应的字符,最后拼接的结果与csrftoken的值进行比较
def _unmask_cipher_token(token):
""" 官方解析64位csrfmiddlewaretoken源码
Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
CSRF_TOKEN_LENGTH, and that its first half is a mask), use it to decrypt
the second half to produce the original secret.
"""
mask = token[:CSRF_SECRET_LENGTH]
token = token[CSRF_SECRET_LENGTH:]
chars = CSRF_ALLOWED_CHARS
pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in mask))
return "".join(chars[x - y] for x, y in pairs) # Note negative values are ok
- 如果要使视图不被csrf保护,则可以对视图函数使用csrf_exempt装饰器(from django.views.decorators.csrf import csrf_exempt)
sql注入
- 使用orm操作数据
- 直接使用sql使,通过变量绑定的方式,尽量不要使用字符串拼接的方式
中间件
自定义中间件
# 使用函数的方式
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)
logger.info("%s %s %s", duration, request.path, request.GET.dict() )
return response
return middleware
# 使用类的方式
# 还可以通过继承from django.utils.deprecation import MiddlewareMixin,然后实现process_request, process_response等方法
class PerformanceAndExceptionLoggerMiddleware:
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.
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
response["X-Page-Duration-ms"] = int(duration * 1000)
logger.info("duration:%s url:%s parameters:%s", duration, request.path, request.GET.dict() )
# Code to be executed for each request/response after
# the view is called.
return response
def process_exception(self, request, exception):
if exception:
message = "url:{url} ** msg:{error} ````{tb}````".format(
url = request.build_absolute_uri(),
error = repr(exception),
tb = traceback.format_exc()
)
logger.warning(message)
# send dingtalk message
dingtalk.send(message)
# capture exception to sentry:
capture_exception(exception)
return HttpResponse("Error processing the request, please contact the system administrator.", status=500)
django-redis
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://192.168.139.133:6379/0",
'TIMEOUT': 60, # default expire time per api call
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"SOCKET_CONNECT_TIMEOUT": 5, # in seconds
"SOCKET_TIMEOUT": 5, # r/w timeout in seconds
'MAX_ENTRIES': 10000,
'KEY_PREFIX': 'recruit-',
# "PASSWORD": "mysecret",
}
}
}
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.cache.UpdateCacheMiddleware', # 更新缓存
'django.middleware.common.CommonMiddleware', # 放在这个的前后
'django.middleware.cache.FetchFromCacheMiddleware', # 从缓存获取
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
集成celery
CELERY_BROKER_URL = 'redis://192.168.139.133:6379/8'
CELERY_RESULT_BACKEND = 'redis://192.168.139.133:6379/1'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'
- 然后在各应用添加tasks.py,celery会自动扫描所有注册的应用下的tasks
# 一个发送简单邮件的示例
'''
# django中邮件相关参数配置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'xxx@163.com'
EMAIL_HOST_PASSWORD = 'xxx'
DEFAULT_FROM_EMAIL = 'xxx@163.com'
'''
from celery import shared_task
from django.core.mail import send_mail
@shared_task
def send_email(email_to, subject, message, from_email=None):
send_mail(subject, message, from_email, email_to, fail_silently=False)
- 启动celery worker, 注意:windows下需要使用 -P gevent,不然任务执行不了
celery -A 应用名称 worker -l info -P gevent
- 可以借助flower对任务进行监控
- 安装:pip install flower
- 启动: celery -A 应用名称 flower
django-celery-beat
- 可以在admin后台管理定时任务
- 安装 pip install django-celery-beat
- 启用
INSTALLED_APPS = [
...,
'django_celery_beat',
]
- 数据库迁移:python manage.py migrate django_celery_beat
- 启动定时任务: celery -A 应用名称 beat --scheduler django_celery_beat.schedulers.DatabaseScheduler
- 添加定时任务
- 在admin后台直接添加,保存到数据库表中
- 在配置中添加
from celery import shared_task
@shared_task
def schedule_test(msg):
print(msg)
app = Celery('wqb_dj')
# 在配置中添加
app.conf.beat_schedule = {
'add-every-10-seconds': {
'task': 'wqb_dj.tasks.add',
'schedule': 10.0,
'args': (16, 4, )
},
}
app = Celery('wqb_dj')
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
# Calls schedule_test('hello') every 10 seconds.
sender.add_periodic_task(10.0, schedule_test.s('hello'), name='hello every 10')
# Calls test('world') every 30 seconds
sender.add_periodic_task(30.0, schedule_test.s('world'), expires=10)
# Executes every Monday morning at 7:30 a.m.
sender.add_periodic_task(
crontab(hour=19, minute=30, day_of_week=1),
schedule_test.s('Happy Mondays!'),
)
import json
from django_celery_beat.models import PeriodicTask, IntervalSchedule
def func_test_add_schedule_task():
"""动态的增加定时任务"""
# 通过周期任务相关的模型类,在运行时添加
# 先创建定时策略
schedule, created = IntervalSchedule.objects.get_or_create(every=10, period=IntervalSchedule.SECONDS, )
# 再创建任务
msg = time.time()
PeriodicTask.objects.create(interval=schedule, name=f'say welcome {msg}', task='wqb_dj.tasks.schedule_test',
args=json.dumps([f'welcome {msg}']), )
图片与文件
MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # 媒体存放目录
MEDIA_URL = '/media/' # 媒体请求路径
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
- 图片:模型中的字段定义使用:models.ImageField(upload_to='图片存储目录/', blank=True, verbose_name='图片')
- 文件:模型中的字段定义使用:models.FileField(upload_to='文件存储目录/', blank=True, verbose_name='文件')
- 最后访问文件或图片的路径就是:/media/图片(文件)存储目录/图片(文件)名
- 还可以使用oss存储
# 阿里云 CDN 存储静态资源文件 & 阿里云存储上传的图片/文件
# STATICFILES_STORAGE = 'django_oss_storage.backends.OssStaticStorage'
DEFAULT_FILE_STORAGE = 'django_oss_storage.backends.OssMediaStorage'
# 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','')
# The name of the bucket to store files in
OSS_BUCKET_NAME = 'xxx'
# 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'