Django官方文档
django 使用 iframe 时,可能浏览器会出现错误,根据提示信息发现是因为 X-Frame-Options=deny 导致的。
Refused to display xxx in a frame because it set 'X-Frame-Options' to 'deny'.
官方文档中关于点击劫持保护
The X-Frame-Options HTTP 响应头是用来给浏览器 指示允许一个页面 可否在 , , 或者 中展现的标记。站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免点击劫持(clickjacking)攻击。它有三个值:
根据上述 X-Frame-Options的三个值描述,只要修改django的X-Frame-Options为SAMEORIGIN ,那么相同域名页面就可以使用frame中展示。
在 settings.py 中的中间件中,启用了 django.middleware.clickjacking.XFrameOptionsMiddleware 中间件即开启了 X-Frame,django 是默认开启的。
MIDDLEWARE = [...'django.middleware.clickjacking.XFrameOptionsMiddleware',...
]
默认情况下,中间件将为每个出站的 HttpResponse将X-Frame-Options 头设置为 DENY。如果希望设置为其他值,可以在 settings.py 中的X_FRAME_OPTIONS设置:
X_FRAME_OPTIONS = 'SAMEORIGIN'
如果在使用此中间件的情况下,希望个别视图不使用 X-Frame ,则可以使用装饰器 xframe_options_exempt
from django.http import HttpResponse
from django.views.decorators.clickjacking import xframe_options_exempt@xframe_options_exempt
def ok_to_load_in_a_frame(request):return HttpResponse("此页面可以使用 iframe 加载")
注意:如果要提交表单或访问框架或 iframe 中的会话 cookie,则可能需要修改 CSRF_COOKIE_SAMESITE 或 SESSION_COOKIE_SAMESITE 设置。
在指定视图中使用 X-Frame,也可以使用装饰器
from django.http import HttpResponse
from django.views.decorators.clickjacking import xframe_options_deny
from django.views.decorators.clickjacking import xframe_options_sameorigin@xframe_options_deny
def view_one(request):return HttpResponse("不能在 iframe 中使用")@xframe_options_sameorigin
def view_two(request):return HttpResponse("可以在同域的 iframe 中使用")
为了防止爬虫程序或其他非人为操作,会使用验证码进行检查,一般常用的验证码方式有:
pillow 库是 python 的一个比较简单方便的图形工具库
pip install pillow
需注意的是,使用 pillow 时,是引入 PIL 库
pillow 有三个主要的类,分别是 Image、ImageDraw 和 ImageFont 类。
Image 类可以创建画布,所有的绘制图形都会在画布上进行。创建画布时需指定画布大小、颜色等信息。
ImageDraw 类用来在画布上进行绘制图案,使用时需指定画笔的颜色、绘制方式(具体的图形或绘制路径)、绘画起始坐标、结束坐标等信息。
ImageFont 类可以将文本绘制为图像,使用时需要指定字体、大小、文本内容等信息
绘制流程一般为创建画布、绘制文本到画布上、绘制干扰图像到画布上。
from PIL import Image, ImageDraw, ImageFontdef new_code_img(request):# 创建画布bg = (220, 220, 180) # 画布背景色,RGBimg = Image.new('RGB', (120, 30), bg) # 创建画布,使用RGB模式,画布大小为120*30,指定背景色# 在画布上创建画笔对象draw = ImageDraw.Draw(img, 'RGB') # 指定画布,创建画笔对象,使用RGB模式# 随机生成4位字符chars = ''while len(chars) < 4:flag = random.randint(0, 2)char = chr(random.randrange(48, 57) if flag == 0 else random.randrange(65, 90) if flag == 1 else random.randrange(97, 122))if len(chars) == 0 or chars.count(char) == 0:chars += char# 保存验证字符到 sessionrequest.session['code'] = chars# 创建字体font = ImageFont.truetype(font='static/font/Fangz.ttf',size=25)# 绘制内容for char in chars:# 指定字体颜色font_color = (random.randrange(255), random.randrange(255), random.randrange(255))# 字符坐标xy = (15 + chars.index(char) * 25, random.randint(0, 5))# 绘制字符draw.text(xy, char,font=font,fill=font_color)# 画干扰点for i in range(50):# 干扰点坐标xy = (random.randrange(120), random.randrange(30))# 干扰点颜色p_color = (random.randrange(255), random.randrange(255), random.randrange(255))# 绘制干扰点draw.point(xy, p_color)# 删除画笔和字体,释放资源del drawdel font# 生成图片对象,返回响应import io# 创建缓冲对象buf = io.BytesIO()# 保存图片到缓冲区img.save(buf, 'png')# 从缓冲区获取图片,添加到响应对象中,并返回return HttpResponse(content=buf.getvalue(), content_type='image/png')
在前端可以使用 img 标签来获取验证码图片,并添加点击重新获取事件
添加参数是为了确保每次请求时路径不一样,获取的都是新的验证码图片。
django 提供了一个很方便的数据分页工具: 分页器 Paginator,用于数据模型的分页。使用时(通常在视图处理函数中)将查询到的数据模型传入即可。使用分页器可以十分方便的获取当前页号、上页下页页号、是否有上下页等信息。
from django.core.paginator import Paginatordef order_list(request):# 获取查询关键字kw = request.GET.get('kw', '')# 获取展示页码page = request.GET.get('p', 1)# 获取查询数据orders = Order.objects.filter(Q(title__icontains=kw)).all()# 创建分页器,参数1是数据模型对象,参数2是每页显示记录数量paginator = Paginator(orders, 5)# 获取分页后的数据,即第几页内的数据pager = paginator.page(page)return render(request, 'list.html', locals())
前端使用 pager.object_list 获取数据,还可以使用 paginator.page_range 获取全部页码,还有一些其他分页信息也能方便的获得。
商品列表 订单查询结果
ID 名称 单价 支付状态 {% for order in pager.object_list %}{{ order.id }} {{ order.title }} {{ order.price }} {{ order.pay_status }} {% empty %}没有查到数据 {% endfor %}
{ pager.previous_page_number }}&kw={{ kw }}" {% endif %}><{% for p in paginator.page_range %}{% if p == pager.number %}{{ p }}{% else %}{ p }}&kw={{ kw }}">{{ p }}{% endif %}{% endfor %}{ pager.next_page_number }}&kw={{ kw }}" {% endif %}>>
django 提供了很多视图处理类,来代替视图处理函数(类似于 tornado)。
一个简单的 View 例子:
from django.views import Viewclass GoodsView(View):# 处理 get 请求def get(self, request):pass# 处理 post 请求def post(self, request):pass
需注意的是,注册路由时注册路由处理类并使用其 as_view() 方法
urlpatterns = [path('goods/', views.GoodsView.as_view(), name='goods')
]
具体的使用方法可以参考官方文档
官方文档-内置的基于类的视图
中间件基于 AOP(面向切面编程)的设计思想,目的是扩展业务,即在不修改原业务的基础上,添加新的功能,有些类似于装饰器。
django 收到一个请求后,会经过一系列的中间件后达到视图函数,经过视图函数处理后,再经过这些中间件后将数据返回给用户浏览器。
中间件处理请求的方法是 process_request,处理响应的方法是 process_response。处理顺序为:接收到请求交给 process_request ,然后交给 url 分发器,之后是 process_view,然后交给视图处理函数,接下来是 process_template_response(不常用),然后是处理数据模型、通过视图渲染模板,再下来是 process_response ,最后将响应发回给浏览器。如果从请求到响应的过程中出现错误,则交给 process_exception 处理,然后返回错误信息到浏览器。
如果请求在通过中间件时, process_request 没有返回值(或返回值为 None),则会继续交给下一步处理;如果有返回值(返回值是 HttpResponse、render、redirect),则会禁止请求往下进行,而将返回值交给本类的 process_response 方法返回请求,直接返回给客户端,所以可以将一些放在视图函数之前或之后的行为写在中间件中。
中间件的处理顺序按照在 settings.py 中注册的顺序,请求是顺序处理,响应是逆序处理。
django 使用的中间件可以在 settings.py 中的 MIDDLEWARE 字段定义。此字段是一个字符串列表,每一个字符串就是引用的中间件类所在。
所有自定义的中间件(类)需继承 django.utils.deprecation.MiddlewareMixin 类。并且要在 settings.py 中添加注册
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirectclass AuthMiddleware(MiddlewareMixin):"""访问验证中间件"""def process_request(self, request):# 如果请求访问 login 页面则不进行检测if request.path_info == '/login/': # request.path_info 能获取请求URLreturn# 1. 读取当前访问用户的 session 信息,如果能读到,说明已经登录过username = request.sesssion.get('username')if username:return# 2. 如果没有登录过,else:return redirect('/login/')def process_view(self, request):passdef process_template_response(self, request, response):passdef process_response(self, request, response):return response
django 提供了信号调度机制,用于监听框架中执行某些操作,实现业务与模型或视图之间解耦。django 中某些动作发生的时候,系统会根据信号定义的函数执行相应的操作。
发送信号有点像是调用函数并传参,只是在发送信号时并不知道需要调用什么函数,所以设立了信号机制。只在有需要时才创建接收信号的函数或方法。
django 内置了一些信号:
| 信号 | 说明 |
|---|---|
| pre_init | model 对象执行其构造方法前,自动触发 |
| post_init | model 对象执行其构造方法后,自动触发 |
| pre_save | model 对象保存前,自动触发 |
| post_save | model 对象保存后,自动触发 |
| pre_delete | model 对象删除前,自动触发 |
| post_delete | model 对象删除后,自动触发 |
| m2m_changed | model 对象使用 ManyToMany 字段操作数据库的第三张表(add/remove) |
| class_prepared | 程序启动时,检测到已注册的model类,对于每一个类,自动触发 |
| pre_migrate | 执行migrate命令前,自动触发 |
| post_migrate | 执行migrate命令后,自动触发 |
| 信号 | 说明 |
|---|---|
| request_started | 请求来到前,自动触发 |
| request_finished | 请求结束后,自动触发 |
| got_request_exception | 请求异常时,自动触发 |
| setting_changed | 配置文件改变时,自动触发 |
| 信号 | 说明 |
|---|---|
| template_rendered | 模板执行渲染时,自动触发 |
| 信号 | 说明 |
|---|---|
| connection_created | 创建数据库连接时,自动触发 |
一般信号写在应用 app 的 __init__.py 文件中。信号函数的参数定义是固定的,例如 sender 表示信息发送对象。django 默认将信号处理程序存储为弱引用,这意味着如果处理程序是一个本地函数,可能会被垃圾回收,所以需要添加 weak=False。
from dango.db.models.signals import post_savedef post_save_func(sender, **kwargs):# 输出相关信息print('保存信息:', sender, kwargs)# 连接信号和处理函数
post_save.connect(post_save_func, weak=False)
也可以使用装饰器的方式
from django.db.models.signals import post_save
from django.dispatch import receiver@receiver(post_save, weak=False)
def post_save_func(sender, **kwargs):# 输出相关信息print('保存信息:', sender, kwargs)
django 信号处理函数主要参数有两个,sender 是信号发送者,kwargs 是个字典,存储了传递的基本信息。例如 instance 为引起信号的实例对象,signal 为传递的信号对象等。
除了 django 预设的信号外,我们也可以自定义一些信号
定义信号就是
from django import dispatch# providing_args 发送信息的参数列表
action = dispatch.Signal(providing_args=['name', 'age'])
注册信号就是将信号发给接收函数
@receiver(action)
def post_action(sender, **kwargs):print(sender, kwargs)
在行为发生时(函数、方法执行中)发送信号。发送的数据除了 sender 外,在定义信号时就定义好了。
action.send(sender='sender', name='Joe', age=18)
django 的日志由版本号(version)、格式化(formatters)、处理器(handlers)、记录器(loggers)、过滤器(filters)五部分组成。django 的日志记录器默认存在"django.server"和"django.request"两个。
配置日志需要在 settings.py 中声明 LOGGING,它是一个字典。
LOGGING = {'version': 1, # 版本号'disable_existing_logger': False, # 是否禁用已经存在的记录器'formatters': { # 声明 格式化输出'simple': { # 声明格式化的名称,后面在记录器中会使用'format': "%(asctime)s %(module)s.%(funcName)s: %(message)s", # 输出格式'datefmt': '%Y-%m-%d %H:%M:%S' # 时间格式}},'handlers': { # 声明处理器,如文件输出、控制台输出、发送邮件等'inf': {'class': 'logging.handlers.TimedRotatingFileHandler', # 处理器类'filename': f'{BASE_DIR}/out.log', # 输出文件名'when': 'WO', # 每周一切割日志'backupCount': 5, # 备份数量'formatter': 'simple', # 使用的格式'level': 'DEBUG' if DEBUG else 'INFO', # 处理级别},'err': {'class': 'logging.handlers.TimedRotatingFileHandler', # 处理器类'filename': f'{BASE_DIR}/err.log', # 输出文件名'when': 'D', # 每天切割日志'backupCount': 5, # 备份数量'formatter': 'verbose', # 使用的格式'level': 'WARNING',},'out': {'class': 'logging.StreamHandler', # 处理器类'formatter': 'simple', # 使用的格式'level': 'INFO',},'file': {'class': 'logging.FileHandler', # 处理器类'formatter': 'simple', # 使用的格式'level': 'WARNING','filename': f'{BASE_DIR}/warn.log','when': 'D', # 每天切割日志'backupCount': 7, # 备份数量}},'loggers': { # 日志记录器'inf': {'handlers': ['inf'], # 使用的处理器,一个记录器能使用多个处理器'level': 'DEBUG', # 记录级别'propagate': True, # 是否传播},'err': {'handlers': ['err'],'level': 'DEBUG','propagate': True,},'django': {'handlers': ['out', 'file'], # 使用的处理器,一个记录器能使用多个处理器'level': 'INFO', # 记录级别'propagate': True, # 是否传播}}
}
配置好后可以使用,一般会在中间件里,例如对请求进行日志记录
import loggingclass LoggingMiddleware(MiddlewareMixin):def process_request(self, request):ip = request.META.get('REMOTE_ADDR')path = request.get_raw_uri()msg = "%s 访问 %s" %(ip, path)# 获取日志记录器 django,并记录 INFO 级消息logging.getLogger('django').info(msg)
django 有多种缓存方案,可以将渲染好的页面、session 等数据存放在缓存中等待调用。缓存也有很多选择,本地、文件服务器、redis 服务器、数据库服务器等。详细的可以查看官方文档。
官方文档-缓存框架
自定义缓存需要在 settings.py 中进行配置,例如
文件缓存:
CACHES = {'default': { # 缓存方案名称'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', # 缓存方案(使用哪种缓存技术)'LOCATION': 'c:/foo/bar', # 数据存储地点名称'TIMEOUT': 300, # 超时时间,单位是秒'OPTIONS': { # 其他的一些选项'MAX_ENTRIES': 300, # 最大的实体数},}
}
内存缓存:
CACHES = {'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache','LOCATION': 'unique-snowflake',}
}
原生缓存用来保存一些临时性的数据,有点像 session,但是是不依赖、不区分客户端的。
from django.core.cache import cachecache.add(key, value, timeout=DEFAULT_TIMEOUT, version=None) # 添加缓存,如果 key 已存在则放弃
cache.set(key, value, timeout=DEFAULT_TIMEOUT, version=None) # 设置缓存 key、value、timeout
cache.set_many(dict, timeout) # 传入字典,一次设置多个缓存
cache.get(key, default=None, version=None) # 获取缓存 key 的 value
cache.get_many(keys, version=None) # keys 是列表,返回字典,返回多个缓存
cache.delete(key, version=None) # 显性删除 key 和其 value
cache.clear() # 清空所有缓存
更多详细的使用方式可以查看官方文档
官方文档-缓存框架-底层缓存API
使用缓存保存渲染好的视图结果,可以使用装饰器 @cache_page ,它将自动缓存视图的响应。
from django.views.decorators.cache import cache_page@cache_page(timeout=60,cache='default',key_prefix=None) # cache为settings.py中设置的缓存方案
def index(request):pass
redis 的特性决定了它是一个高速缓存的分布式数据库,所以大多数的框架缓存都会使用 redis。django 使用 redis 可以使用其插件 django-redis,然后在 settings.py 的 CACHES 中进行设置
pip install django-redis
CACHES = {'redis': { # 缓存方案名称'BACKEND': 'django_redis.cache.RedisCache', # 缓存方案'LOCATION': 'redis://127.0.0.1:6379/1', # redis 的主机地址、端口和数据库编号'OPTIONS': { # 其他的一些选项'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接客户端'PASSWORD': 'mysecret', # 口令'SOCKET_CONNECT_TIMEOUT': 5, # 连接超时时间,单位秒'SOCKET_TIMEOUT': 5, # 读写超时时间,单位秒'CONNECTION_POOL_KWARGS': {'max_connections': 10,} # 连接池参数},}
}
当配置好缓存信息后,可以在代码中使用连接池
from django.core.cache import get_cache
from django_redis import get_redis_connectionr = get_redis_connection('redis')
connection_pool = r.connection_pool
Celery是由Python开发、简单、灵活、可靠的分布式任务队列,是一个处理异步任务的框架,其本质是生产者消费者模型,生产者发送任务到消息队列,消费者负责处理任务。Celery侧重于实时操作,但对调度支持也很好,其每天可以处理数以百万计的任务。
celery 官方英文文档
非官方中文文档
由于 django 框架请求/响应的过程是同步的,框架本身无法实现异步响应。所以异步执行前端一般会使用 Ajax,后端则使用 Celery。另外 django-celery 插件已经有一段时间没有更新了,对于新版本的 python 和 django 会有适配问题,所以使用 celery
一些参考文档
celery + redis
django-celery 使用步骤:
首先安装 django-celery,因为 celery 本身不实现中间件 Broker 的功能,所以还需要使用中间件。django 和 redis 配合的不错,这里就使用 django-redis
pip install django-celery django-redis
这里需要注意的是,windows 在 celery4.0 之后不支持多进程方式,而是更换成了协程方式,所以要使用 eventlet 或 gevent。
pip install eventlet
然后在 settings.py 中添加配置信息
##### celery 配置 #####
from urllib.parse import quote
PASSWORD = quote('123456') #使用有特殊字符密码,带有特殊字符需要进行转换才能识别# Broker配置,使用Redis作为消息中间件
BROKER_URL = f'redis://mast:{PASSWORD}@127.0.0.1:6379/8'.format(PASSWORD) # 此格式为连接需要验证的 redis
# 后台结果,如果没有此参数则使用 orm 的数据库
CELERY_RESULT_BACKEND = f'redis://mast:{PASSWORD}@127.0.0.1:6379/9'.format(PASSWORD)
# 结果序列化方案
CELERY_RESULT_SERIALIZER = 'json'
# 任务结果过期时间,秒
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
# 指定导入的任务模块,可以指定多个
CELERY_IMPORTS = ('app.tasks',) # 参数为 tasks.py 文件路径(即 应用.tasks)
CELERY_TIMEZONE = ’Asia/Shanghai' # 设置时区CELERYD_LOG_FILE = BASE_DIR + "/logs/celery/celery.log" # log路径
CELERYBEAT_LOG_FILE = BASE_DIR + "/logs/celery/beat.log" # beat log路径# 设置定时器策略
from datetime import timedeltaCELERYBEAT_SCHEDULE = {# 定时任务一u'邮件发送': { # 任务名称'task': 'app.tasks.print_now', # 需要执行的任务函数# 'schedule': crontab(minute='*/2'), # 延迟'schedule': timedelta(seconds=5), # 间隔5秒'args': ('现在时间',), # 参数},
}
# schedule 参数是执行频率,可以是整型(秒)、timedelta对象、crontab对象
# 还可以设置 kwargs 字典型的关键字参数
# 对于 crontab 函数使用举例
# crontab(hour='*/24') 每隔24小时
# crontab(minute=30, hour=0) 每天的凌晨 00:30
# crontab(hour=6, minute=0, day_of_month='1') 每月1号的 6:00
在主工程目录(settings.py 所在目录)添加 celery.py,主体代码就写在这里
import os
from celery import Celery
from django.conf import settings# 设置项目运行的环境变量 DJANGO_SETTINGS_MODULE
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'DAdmin.settings') # 将 DAdmin.settings 添加到环境变量,根据工程名称改变# 创建 celery 应用对象
app = Celery('AdminCelery')# 加载配置
app.config_from_object('django.conf:settings')# 如果在工程的应用中创建了tasks.py模块,那么celery应用会自动去添加任务
# 比如添加了一个任务,在 django 中会实时地检索出来
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
然后需要在项目中加载 celery 的 app,在项目主工程目录的 init.py 中,添加 all 属性
from .celery import app as celery_app #引入 celery.py 里的 app 对象# 向项目模块中增加 celery_app 对象
__all__ = ['celry_app']
每个任务本质上就是一个函数,写在 tasks.py 中。
import datetime
from celery import shared_task
import logging
logger = logging.getLogger(__name__)# 定时任务,在 settings.py 的 CELERYBEAT_SCHEDULE 中注册
# 参数 info 是在注册时传入
# 调用时也可以触发
@shared_task
def print_now(info): print(info, datetime.datetime.now())# 也可以输出日志logger.info(info, datetime.datetime.now())# 没有在 CELERYBEAT_SCHEDULE 中注册,只能通过调用触发
@shared_task
def add(x, y):time.sleep(5)return x + y
celery 的迁移和 django 其他的一样
python manage.py makemigrations
python manage.py migrate
接下来就是启动 celery 服务 worker,用来获取消息和执行任务
celery -A django_test worker -l info
mac、linux 系统使用此命令,-A 指定工程项目,-l 指定日志等级为 info
celery -A django_test worker -l info -P eventlet
windows 使用此命令,-A 指定工程项目,-P 指定执行单元使用 eventlet 实现后台异步操作,-l 指定日志等级为 info
然后是 beat,启动后就能够执行定时任务了
celery -A django_test beat -l info
非定时任务必须进行调用
from .tasks import add # 引入写好的任务def add(request): # 视图函数...task = add.delay(100, 200) # 执行任务,返回任务异步结果对象return HttpResponse(json.dumps({'status': 'ok','task_id': task.task_id}),'application/json')
当调用时立刻能获得异步结果对象,此时对象的状态为 PENDING ,结果为 None。
该对象的一些常用的属性和方法:
通常获取该对象的 task_id 然后可以在另一函数中使用 task_id 获取任务状态和结果
from celery import resultdef get_result_by_taskid(request):task_id = request.GET.get('task_id')# 获取异步结果对象ar = result.AsyncResult(task_id)if ar.ready():return JsonResponse({'status': ar.state, 'result': ar.result})else:return JsonResponse({'status': ar.state, 'result': ''})
celery 的结果可以使用 django-celery-results 包来方便的保存至数据库
Celery可通过task绑定到实例获取到task的上下文,这样我们可以在task运行时候获取到task的状态,记录相关日志等
from celery import shared_task
import logging
logger = logging.getLogger(__name__)# 任务绑定
@shared_task(bind=True) # bind=True 设置任务绑定
def add(self,x, y): # 第一个参数 self 能获取任务实例对象try:logger.info('add__-----'*10)logger.info('name:',self.name)logger.info('dir(self)',dir(self))raise Exceptionexcept Exception as e:# 出错每4秒尝试一次,总共尝试4次self.retry(exc=e, countdown=4, max_retries=4) return x + y
Celery在执行任务时,提供了钩子方法用于在任务执行完成时候进行对应的操作,在Task源码中提供了很多状态钩子函数如:on_success(成功后执行)、on_failure(失败时候执行)、on_retry(任务重试时候执行)、after_return(任务返回时候执行)
from celery import Taskclass MyHookTask(Task):def on_success(self, retval, task_id, args, kwargs):logger.info(f'task id:{task_id} , arg:{args} , successful !')def on_failure(self, exc, task_id, args, kwargs, einfo):logger.info(f'task id:{task_id} , arg:{args} , failed ! erros: {exc}')def on_retry(self, exc, task_id, args, kwargs, einfo):logger.info(f'task id:{task_id} , arg:{args} , retry ! erros: {exc}')# 在对应的task函数的装饰器中,通过 base=MyHookTask 指定
@shared_task(base=MyHookTask, bind=True)
def add(self,x, y):logger.info('add__-----'*10)logger.info('name:',self.name)logger.info('dir(self)',dir(self))return x + y
flower 是 celery 的一个图形化管理界面
django2集成DjangoUeditor富文本编辑器
REST 即表述性状态传递(Representational State Transfer),它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准。
RESTful API 设计规范
官方网站
中文文档
RESTful API四大基本原则:
django-RESTful 需要以下包支持(除了主插件程序包外,其他的包为可选项)
可以根据需要安装相应的包
pip install djangorestframework
pip install markdown # 为browsable API 提供Markdown支持。
pip install django-filter # Filtering支持。
使用 django-RESTful 需要注册应用到 settings.py 的 INSTALLED_APPS 中
INSTALLED_APPS = [...'rest_framework',
]
REST framework API的所有全局设定都会放在一个叫REST_FRAMEWORK的配置词典里,并添加到 settings.py 中:
REST_FRAMEWORK = {# 在这里配置访问许可'DEFAULT_PERMISSION_CLASSES': [# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' # 匿名只读,登录用户可用'rest_framework.permissions.IsAdminUser', # 只能管理员使用],# 配置分页器'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination','PAGE_SIZE': 10,
}
如果打算用browsable API(一个可视化API测试工具),可能也会用REST framework的登录注销视图。可以添加路由到根目录的urls.py文件
urlpatterns = [...path('api-auth/', include('rest_framework.urls')) # 路由路径可以更改
]
以一个实例进行测试:创建一个读写API来访问项目的用户信息。
在根目录的 urls.py 中创建API
# urls.py from django.urls import path, include
from django.contrib.auth.models import User
from rest_framework import routers, serializers, viewsets# 序列化器是用来定义API的表示形式。
class UserSerializer(serializers.HyperlinkedModelSerializer):class Meta:model = Userfields = ['url', 'username', 'email', 'is_staff']# ViewSets定义视图的行为。
class UserViewSet(viewsets.ModelViewSet):queryset = User.objects.all()serializer_class = UserSerializer# 路由器提供一个简单自动的方法来决定URL的配置。
router = routers.DefaultRouter()
router.register(r'users', UserViewSet) # 注册路由# 通过URL自动路由来给我们的API布局。
# 此外,我们还要把登录的URL包含进来。
urlpatterns = [path('', include(router.urls)), # 注册 REST 路由到主路由path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
现在可以在浏览器中打开(默认页,即第一条路由)http://127.0.0.1:8000/,查看 ‘user’ API了。如果使用了右上角的登录控制,还可以在系统中添加、创建并删除用户。这样就是安装和配置成功了。
在实际项目中,django-RESTful 的使用分为三个步骤:
定义序列化程序,可以创建一个新的文件,这里使用 serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers# 定义序列化处理器,继承自 serializers.HyperlinkedModelSerializer,使用超链接关系
class UserSerializer(serializers.ModelSerializer):class Meta:# model 定义使用的数模型model = User# fields 定义使用数据模型的哪些字段fields = ('url', 'username', 'email', 'groups')
django-RESTful 提供了3个序列化类的模板: Serializer、ModelSerializer、HyperlinkedModelSerializer
create(**validate_data) 和 update(instance, **validate_data) 两个函数django-RESTful 提供了一些预设的视图处理类
from rest_framework import viewsets
from django.contrib.auth.models import User
from .serializers import UserSerializer# 视图处理类
class UserViewSet(viewsets.ModelViewSet):# queryset 为查询的结果集合queryset = User.objects.all()# serializer_classs 使用的序列化器serializer_class = UserSerializer
可以在应用中创建路由,并注册到主路由中
# 应用中的 urls.pyfrom django.urls import path, include
from rest_framework import routers# 路由器提供一个简单自动的方法来决定URL的配置。
# 创建路由器
router = routers.DefaultRouter()
# 在路由器中注册路由及处理器(FBV或CBV)
router.register(r'users', UserViewSet)# 通过URL自动路由来给我们的API布局。
urlpatterns = [path('', include(router.urls)), # 将 REST 路由器中的路由添加到 app 的路由中path('api-auth/', include('rest_framework.urls')), # 添加 browsable API 的登录路由
]
根据实际情况自定义序列化器或者使用自定义的视图函数,有一些需要使用到的
在自定义的视图中,需要将数据通过序列化器序列化后发出响应,接收到的请求也需要通过反序列化获取具体数据
from .serializers import UserSerializer
from django.contrib.auth.models import Users1 = UserSerializer(User.objects.get(pk=1)) # 序列化单条数据
s2 = UserSerializer(User.objects.all(), many=True) # 序列化多条数据# 渲染成Json字符串
from rest_framework.renderers import JSONRenderer
content = JSONRenderer().render(s1.data)
# 对已经渲染的Json字符串反序列化为Json对象
data = JSONParser().parse(io.BytesIO(content))
# 或直接解析 request 对象(POST)
# from rest_framework.parsers import JSONParser
# data = JSONParser().parse(request)
# serializer = UserSerializer(data=data) # 根据提交的参数获取序列化对象
# if serizlizer.is_valid(): # 通过验证
# serializer.save() # 保存数据
在查看和测试API的过程中,会发现某些数据表的外键指向的是关联表的数据链接,而不是数据内容。如果要使用数据内容,就需要将关联关系序列化。在定义序列化器时,将外键字段声明成为字符串关系字段即可。
from django.contrib.auth.models import User
from rest_framework import serializers# 定义序列化处理器,继承自 serializers.HyperlinkedModelSerializer,使用超链接关系
class UserSerializer(serializers.HyperlinkedModelSerializer):# 在此定义字段# 定义关系字段,将数据信息字符串化groups = serializers.StringRelatedField() # 如果是对多关系(对方表是多端)则需要参数 many=True# 也可以将关系对象整体序列化(即返回的 json 中,此字段是给包含了所有数据的 json 对象)# groups = GroupSerializer() # 需先定义 GroupSerializer 序列化器,且如果对方是多端也需要 many=Trueclass Meta:# model 定义使用的数模型model = User# fields 定义使用数据模型的哪些字段fields = ('url', 'username', 'email', 'groups')
这个方法在一对一、一对多、多对多关系都适用。如果对方是多端(即此表的该外键字段或反查字段有多个数据),添加参数 many=True 即可。其中一些关联字段类型为
使用的封装好的django-RESTful视图处理类能够方便的获取请求返回响应,但是如果需要使用自定义的视图处理器,则可使用 @api_view 装饰器(FBV)或 APIView 基类(CBV)。
FBV 方式举例,需注意的是注册路由使用 path 方法直接到 urlpatterns
from rest_framework.decorators import api_view
from .serializers import UserSerializer
from rest_framework.response import Response
from rest_framework.parsers import JSONParser
from django.contrib.auth.models import User
from django.http import JsonResponse@api_view(['GET', 'POST'])
def user_func(request, pk=None):if request.method == 'GET':if not pk:queryset = User.objects.all()serializer = UserSerializer(queryset, many=True, context={'request': request})else:queryset = User.objects.get(pk=pk)serializer = UserSerializer(queryset, context={'request': request})# django-RESTful 重新封装了 Response,可以直接序列化# 可以根据请求类型返回数据,例如浏览器直接请求会返回管理页面,请求 json 格式则返回 json 数据# 也可以在请求地址后添加参数 ?format=json 指定获取 json 数据或 api 页面return Response(serializer.data)# return JsonResponse(serializer.data) # 返回的是序列化后的json字符串elif request.method == 'POST':# 接收的 request 是 django-RESTful 重新封装后的 request 对象# 可以直接将其内容反序列化,然后通过序列化器从数据库中查询相关数据,再进行序列化data = JSONParser().parse(request)serializer = UserSerializer(data, context={'request': request})# 序列化器对象可以进行验证if serializer.is_valid():serializer.save() # 保存至数据库return Response(serializer.data, status=201)else:return Response(serializer.errors, status=400)
CBV 方式举例,需注意的是注册路由使用 path 方法直接到 urlpatterns
from rest_framework.views import APIView
from .serializers import UserSerializer
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.parsers import JSONParserclass UserClass(APIView):def get(self, request, pk=None):if not pk:queryset = User.objects.all()serializer = UserSerializer(queryset, many=True, context={'request': request})else:queryset = User.objects.get(pk=pk)serializer = UserSerializer(queryset, context={'request': request})return Response(serializer.data)def post(self,request):data = JSONParser().parse(request)serializer = UserSerializer(data, context={'request': request})# 序列化器对象可以进行验证if serializer.is_valid():serializer.save() # 保存至数据库return Response(serializer.data, status=201)else:return Response(serializer.errors, status=400)
默认情况下, APIView 中的相关接口方法不验证权限(授权),对资源并不安全,所以需要增加验证。
首先在 settings.py 中的 REST_FRAMEWORK 字段配置权限访问许可
# settings.pyREST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': [ # 默认使用的授权认证'rest_framework.authentication.BasicAuthentication', # 基本授权认证'rest_framework.authentication.SessionAuthentication', # 基于session的授权认证]
}
然后可以在视图中指定验证方式和许可类型
class EcampleView(APIView):authentication_classes = (SessionAuthentication, BasicAuthentication) # 验证方式permission_classes = (IsAuthenticated,) # 许可类型def get(self, request):pass
但是这种授权是基于 session 登录的,即 auth 模块的认证。在 api 接口中,通常会使用 token 进行认证。
TokenAuthentication 提供了简单的基于 Token 的HTTP认证方案,适用于客户端 - 服务器设置,如本地桌面和移动客户端。要在 django-RESTful 中使用 TokenAuthentication,需要配置认证类包含 TokenAuthentication,另外需要注册 rest_framework.authtoken 这个 app。需注意的是,确保在修改设置后运行一下 manage.py migrate ,因为 rest_framework.authtoken 会提交一些数据库迁移操作。
# settings.py
INSTALLED_APPS = [...'rest_framework.authtoken',
]
REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': [ # 默认使用的授权认证'rest_framework.authentication.TokenAuthentication', # token授权认证]
}
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import Useruser = User.objects.get(pk=1) # 获取用户
token = Token.objects.create(user=user) # 根据用户创建 token 实例
print(token.key) # token.key 就是需要验证的字段
通常会在每个用户创建时,创建对应的 token,可以捕捉用户的 post_save 信号
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):if created:Token.objects.create(user=instance)
也可以为现有用户生成令牌
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Tokenfor user in User.objects.all():Token.objects.get_or_create(user=user)# 此方法可以返回2个值,分别为 token 对象和 created
对客户端进行身份验证,token需要包含在名为 Authorization 的HTTP头中。密钥应该是以字符串"Token"为前缀,以空格分割的两个字符串。例如:
function ajax_get() {fetch('/api/user/, {headers: {'Authorization': 'Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'}}).then(response=>response.json()).then(data=>{console.log(data)})
}
如果认证成功,TokenAuthentication 提供以下认证信息:
那些被拒绝的未经身份验证的请求会返回使用适当WWW-Authenticate标头的HTTP 401 Unauthorized响应。例如:
WWW-Authenticate: Token
当使用TokenAuthentication时,可能希望为客户端提供一个获取给定用户名和密码的令牌的机制。 REST framework 提供了一个内置的视图来提供这个功能。要使用它,需要将 obtain_auth_token 视图添加到你的URLconf:
from rest_framework.authtoken import views
urlpatterns += [path('api-token-auth/', views.obtain_auth_token) # url 路由可以自定义
]
当使用form表单或JSON将有效的username和password字段POST提交到视图时,obtain_auth_token 视图将返回JSON响应:
{ 'token' : '9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b' }
请注意,默认的obtain_auth_token视图显式使用JSON请求和响应,而不是使用settings中配置的默认渲染器和解析器类。如果需要自定义版本的obtain_auth_token视图,可以通过重写ObtainAuthToken类,并在url conf中使用它来实现。默认情况下,没有权限或限制应用于obtain_auth_token视图。如果你希望应用限制,则需要重写视图类,并使用throttle_classes属性包含它们。